在iOS开发中,我们经常使用Category来解决一些问题,但是在使用的过程中,category内部是如何进行处理的,我们来进行简单的了解,如有不足之处还望在评论区指导。
1、Category真面目
我们知道,所有的OC类和对象,在runtime层都是用struct表示的,category也不例外,在runtime层,category用结构体category_t(在objc-runtime-new.h中可以找到此定义),它包含了:
- 1)、类的名字(name)
- 2)、类(cls)
- 3)、category中所有给类添加的实例方法的列表(instanceMethods)
- 4)、category中所有添加的类方法的列表(classMethods)
- 5)、category实现的所有协议的列表(protocols)
- 6)、category中添加的所有属性(instanceProperties)
typedef struct category_t {
const char name;
classref_t cls;
struct method_list_t instanceMethods;
struct method_list_t classMethods;
struct protocol_list_t protocols;
struct property_list_t instanceProperties;
} category_t;
从category的定义也可以看出category的可为(可以添加实例方法,类方法,甚至可以实现协议,添加属性)和不可为(无法添加实例变量)。
2、Category加载
Objective-C的运行是依赖OC的runtime的,而OC的runtime和其他系统库一样,是OS X和iOS通过dyld动态加载的。
- 把category的实例方法、协议以及属性添加到类上
- 把category的类方法和协议添加到类的metaclass上
attachCategoryMethods做的工作相对比较简单,它只是把所有category的实例方法列表拼成了一个大的实例方法列表,然后转交给了attachMethodLists方法。
1、category的方法没有“完全替换掉”原来类已经有的方法,也就是说如果category和原来类都有methodA,那么category附加完成之后,类的方法列表里会有两个methodA。
2、category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会罢休^_^,殊不知后面可能还有一样名字的方法。
3、Category中的+load()方法
如果我们创建了一个类的几个category,每个category都实现了+load方法,每个category中又都写了类中的方法,是如何调用的哪?
1、可以调用,因为附加category到类的工作会先于+load方法的执行
2、+load的执行顺序是先类,后category,而category的+load执行顺序是根据编译顺序决定的。 目前的编译顺序是这样的
按照当前的顺序,Category1会比Category2先编译,后编译的方法会被调用,如果调整category1和category2的顺序就会调用category1中的方法。
@implementation Person (Category)
-(void)showMyself {
NSLog(@"第一个categoryshowMyself");
}
+(void)load {
NSLog(@"第一个category中的load");
}
@end
@implementation Person (Category2)
-(void)showMyself {
NSLog(@"第二个categoryshowMyself");
}
+(void)load {
NSLog(@"第二个category中的load");
}
@end
虽然对于+load的执行顺序是这样,但是对于“覆盖”掉的方法,则会先找到最后一个编译的category里的对应方法。
4、Category方法覆盖
如何调用原来类中被categiry覆盖的方法?只要顺着方法列表找到最后一个对应方法名字就可以调用了。
Class currentClass = [MyClass class];
MyClass *my = [[MyClass alloc] init];
if (currentClass) {
unsigned int methodCount;
Method *methodList = class_copyMethodList(currentClass, &methodCount);
IMP lastImp = NULL;
SEL lastSel = NULL;
for (NSInteger i = 0; i < methodCount; i++) {
Method method = methodList[i];
NSString *methodName = [NSString stringWithCString:sel_getName(method_getName(method))
encoding:NSUTF8StringEncoding];
if ([@"printName" isEqualToString:methodName]) {
lastImp = method_getImplementation(method);
lastSel = method_getName(method);
}
}
typedef void (*fn)(id,SEL);
if (lastImp != NULL) {
fn f = (fn)lastImp;
f(my,lastSel);
}
free(methodList);
}
5、Category关联对象
在objc-references.mm文件中有个方法_object_set_associative_reference,我们可以看到所有的关联对象都由AssociationsManager管理的。
class AssociationsManager {
static OSSpinLock _lock;
static AssociationsHashMap *_map; *// associative references: object pointer -> PtrPtrHashMap.*
public:
AssociationsManager() { OSSpinLockLock(&_lock); }
~AssociationsManager() { OSSpinLockUnlock(&_lock); }
AssociationsHashMap &**associations**() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};
AssociationsManager里面是由一个静态AssociationsHashMap来存储所有的关联对象的。这相当于把所有对象的关联对象都存在一个全局map里面。而map的的key是这个对象的指针地址(任意两个不同对象的指针地址一定是不同的),而这个map的value又是另外一个AssociationsHashMap,里面保存了关联对象的kv对。
runtime的销毁对象函数objc_destructInstance里面会判断这个对象有没有关联对象,如果有,会调用_object_remove_assocations做关联对象的清理工作。
void objc_destructInstance(id obj)
{
if (obj) {
Class isa_gen = _object_getClass(obj);
class_t *isa = newcls(isa_gen);
*// Read all of the flags at once for performance.*
bool cxx = hasCxxStructors(isa);
bool assoc = !UseGC && _class_instancesHaveAssociatedObjects(isa_gen);
*// This order is important.*
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
if (!UseGC) objc_clear_deallocating(obj);
}
return obj;
}