category用结构体category_t表示(在objc-runtime-new.h中可以找到此定义):
1 2 3 4 5 6 7 8 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; };
而类的结构体:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 struct objc_class { Class _Nonnull isa OBJC_ISA_AVAILABILITY; #if !__OBJC2__ Class _Nullable super_class OBJC2_UNAVAILABLE; const char * _Nonnull name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE; struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE; struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE; struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE; typedef struct objc_class *Class;
从category的定义也可以看出category的可为(可以添加实例方法,类方法,甚至可以实现协议,添加属性)和不可为(无法添加实例变量)。
MyClass.h
1 2 3 4 5 6 7 8 9 10 11 12 13 @interface MyClass : NSObject- (void)printName; @end @interface MyClass (MyAddition)@property (nonatomic, copy) NSString *name;- (void)printName ;@end
MyClass.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @implementation MyClass - (void )printName { NSLog (@"%@" , @"MyClass" ); } @end @implementation MyClass (MyAddition )- (void )printName { NSLog (@"%@" , @"MyAddition" ); } @end
转换为.cpp文件:clang -rewrite-objc MyClass.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 static struct { unsigned int entsize; unsigned int method_count; struct _objc_method method_list[1 ]; } _OBJC_$_CATEGORY_INSTANCE_METHODS_MyClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const" ))) = { sizeof (_objc_method), 1 , {{(struct objc_selector *)"printName" , "v16@0:8" , (void *)_I_MyClass_MyAddition_printName}} }; static struct { unsigned int entsize; unsigned int count_of_properties; struct _prop_t prop_list[1 ]; } _OBJC_$_PROP_LIST_MyClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const" ))) = { sizeof (_prop_t ), 1 , {{"name" ,"T@\"NSString\",C,N" }} }; extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_MyClass;static struct _category_t _OBJC_$_CATEGORY_MyClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const" ))) = { "MyClass" , 0 , (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_MyClass_$_MyAddition, 0 , 0 , (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_MyClass_$_MyAddition, }; static void OBJC_CATEGORY_SETUP_$_MyClass_$_MyAddition(void ) { _OBJC_$_CATEGORY_MyClass_$_MyAddition.cls = &OBJC_CLASS_$_MyClass; } #pragma section(".objc_inithooks$B" , long, read, write) __declspec(allocate (".objc_inithooks$B" )) static void *OBJC_CATEGORY_SETUP[] = { (void *)&OBJC_CATEGORY_SETUP_$_MyClass_$_MyAddition, }; static struct _class_t *L_OBJC_LABEL_CLASS_$ [1 ] __attribute__((used, section ("__DATA, __objc_classlist,regular,no_dead_strip" )))= { &OBJC_CLASS_$_MyClass, }; static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1 ] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip" )))= { &_OBJC_$_CATEGORY_MyClass_$_MyAddition, }; static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0 , 2 };
多个category的情况:
1 2 3 4 5 6 7 static struct _class_t *L_OBJC_LABEL_CLASS_$ [1 ] __attribute__ ((used, section ("__DATA, __objc_classlist,regular,no_dead_strip" )))= { &OBJC_CLASS_$_MyClass , }; static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [2 ] __attribute__ ((used, section ("__DATA, __objc_catlist,regular,no_dead_strip" )))= { &_OBJC_$_CATEGORY_MyClass_ $_MyAddition1 , &_OBJC_$_CATEGORY_MyClass_ $_MyAddition2 , };
category的加载 先拿到类的类别数组列表[cat1, cat2, cat3],这个数组元素的顺序是根据编译顺序得来.然后倒序拼装得到一个方法数组列表.最后该方法列表再追加类自身的方法.类别加载结束.因此当有方法覆盖时,执行的将是最后一个编译的类别里的方法.
在类和category中都可以有+load方法,那么有两个问题:
在类的+load方法调用的时候,我们可以调用category中声明的方法么?
这么些个+load方法,调用顺序是咋样的呢?
A:可以调用,因为附加category到类的工作会先于+load方法的执行.
1 2 3 4 5 6 7 8 9 10 objc [20522 ]: LOAD: category 'MyClass(MyCategory1)' scheduled for +loadobjc [20522 ]: LOAD: category 'MyClass(MyCategory2)' scheduled for +loadobjc [20522 ]: LOAD: +[MyClass(MyCategory1) load]2018 -01 -17 16 :28 :01 .451 categoryDemo[20522 :194333 ] MyCategory1:<MyClass: 0 x7f9422c01c30>2018 -01 -17 16 :28 :01 .452 categoryDemo[20522 :194333 ] <MyClass: 0 x7f9422c01c30>-MyClass,小明objc [20522 ]: LOAD: +[MyClass(MyCategory2) load]2018 -01 -17 16 :28 :01 .453 categoryDemo[20522 :194333 ] MyCategory2:<MyClass: 0 x7f9422f00100>2018 -01 -17 16 :28 :01 .453 categoryDemo[20522 :194333 ] <MyClass: 0 x7f9422f00100>-MyClass,小刚
+load的执行顺序是先类,后category,而category的+load执行顺序是根据编译顺序决定的。
怎么调用到原来类中被category覆盖掉的方法? category其实并不是完全替换掉原来类的同名方法,只是category在方法列表的前面而已,所以我们只要顺着方法列表找到最后一个对应名字的方法,就可以调用原来类的方法.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 Class currentClass = [MyClass class ]; MyClass *my = [[MyClass alloc] init]; my.name = @"无脑刚" ; 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 ]; NSLog (@"方法:%@" , methodName); 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); }
打印如下:
1 2 3 4 5 6 7 8 9 10 2018 -01 -17 16 :51 :56 .436 categoryDemo[20716 :206487 ] 方法:printName2018 -01 -17 16 :51 :56 .436 categoryDemo[20716 :206487 ] 方法:introduceYouselfForCategory22018 -01 -17 16 :51 :56 .437 categoryDemo[20716 :206487 ] 方法:printName2018 -01 -17 16 :51 :56 .437 categoryDemo[20716 :206487 ] 方法:introduceYouselfForCategory12018 -01 -17 16 :51 :56 .437 categoryDemo[20716 :206487 ] 方法:printName2018 -01 -17 16 :51 :56 .437 categoryDemo[20716 :206487 ] 方法:.cxx_destruct2018 -01 -17 16 :51 :56 .437 categoryDemo[20716 :206487 ] 方法:name2018 -01 -17 16 :51 :56 .437 categoryDemo[20716 :206487 ] 方法:setName:2018 -01 -17 16 :51 :56 .438 categoryDemo[20716 :206487 ] <MyClass: 0 x7f8a0d618f40>-MyClass,无脑刚
可以看到调用到了类自身的方法.
在category里面是无法为category添加实例变量的。但是我们很多时候需要在category中添加和对象关联的值,这个时候可以求助关联对象来实现。
但是关联对象又是存在什么地方呢? 如何存储? 对象销毁时候如何处理关联对象呢?
所有的关联对象都由AssociationsManager管理. AssociationsManager里面是由一个静态AssociationsHashMap来存储所有的关联对象的。这相当于把所有对象的关联对象都存在一个全局map里面。而map的的key是这个对象的指针地址(任意两个不同对象的指针地址一定是不同的),而这个map的value又是另外一个AssociationsHashMap,里面保存了关联对象的kv对.
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy)
部分实现:
1 2 3 4 5 ObjectAssociationMap *refs = new ObjectAssociationMap; associations[disguised_ object ] = refs; (*refs)[key] = ObjcAssociation(policy, new _ value ); _ class _ setInstancesHaveAssociatedObjects(_ object _ getClass(object ));
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void *objc_destructInstance (id obj) { if (obj) { bool cxx = obj-> hasCxxDtor (); bool assoc = !UseGC && obj-> hasAssociatedObjects (); bool dealloc = !UseGC; if (cxx) object_cxxDestruct (obj); if (assoc) _object_remove_assocations(obj); if (dealloc) obj-> clearDeallocating (); } return obj; }
runtime的销毁对象函数objc_destructInstance
里面会判断这个对象有没有关联对象,如果有,会调用_object_remove_assocations
做关联对象的清理工作。
关于Method,IMP,SEL,self,_cmd struct objc_method 在runtime.h里有申明.
1 2 3 4 5 6 7 struct objc_method typedef struct objc_method *Method ;
附录: 苹果开源网址
objc category的秘密
深入理解Objective-C:Category
macOS 10.13,objc4/objc4-723版本 已经找不到attachMethodLists()的实现了.static void
attachMethodLists(Class cls, method_list_t **addedLists, int addedCount,
bool baseMethods, bool methodsFromBundle,
bool flushCaches)
得看旧版:macOS 10.10,objc4/objc4-646版本