分类的实现原理,编译时的表现,运行时的加载原理 参考文档: objc4-723
category的主要作用是为已经存在的类添加方法。可以把类的实现分开在几个不同的文件里面。
Category 结构体 Category用Category_t结构体定义:
1 2 3 4 5 6 7 8 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的可为(可以添加实例方法,类方法,甚至可以实现协议,添加属性)和不可为(无法添加实例变量)。
Category 与 Extension extension 看起来很像一个匿名的category,但是extension和有名字的category几乎完全是两个东西。 extension在编译期决议,它就是类的一部分,在编译期和头文件里的@interface以及实现文件里的@implement一起形成一个完整的类,它伴随类的产生而产生,亦随之一起消亡。extension一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加extension,所以你无法为系统的类比如NSString添加extension。
但是category 则完全不一样,它是在运行期决议的。 就category和extension的区别来看,我们可以推导出一个明显的事实,extension可以添加实例变量,而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 26 27 28 29 30 31 32 33 34 35 #import <Foundation/Foundation.h> @interface OneClass : NSObject -(void )oneMethod; @end @interface OneClass (OneCategory )@property (nonatomic , copy ) NSString *oneCategoryProp;-(void )oneCategoryMethod; @end #import "OneClass.h" @implementation OneClass -(void )oneMethod{ NSLog (@" oneMethod " ); } @end @implementation OneClass (OneCategory )-(void )oneCategoryMethod{ NSLog (@" oneCategoryMethod " ); } @end
然后使用Clang命令编译下,命令如下: 1 clang -rewrite-objc OneClass.m
然后得到一个3m多的OneClass.app文件……
在OneClass.app中找到相关的代码片段:
生成了实例方法列表:
1 2 3 4 5 6 7 8 9 static struct { unsigned int entsize; unsigned int method_count; struct _objc_method method_list[1 ]; } _OBJC_$_CATEGORY_INSTANCE_METHODS_OneClass_$_OneCategory __attribute__ ((used, section ("__DATA,__objc_const" ))) = { sizeof (_objc_method), 1 , {{(struct objc_selector *)"oneCategoryMethod" , "v16@0:8" , (void *)_I_OneClass_OneCategory_oneCategoryMethod}} };
生成了属性列表:
1 2 3 4 5 6 7 8 9 static struct { unsigned int entsize; unsigned int count_of_properties; struct _prop_t prop_list[1 ]; } _OBJC_$_PROP_LIST_OneClass_$_OneCategory __attribute__ ((used, section ("__DATA,__objc_const" ))) = { sizeof (_prop_t ), 1 , {{"oneCategoryProp" ,"T@\"NSString\",C,N" }} };
生成了category本身 ,并用前面的实例方法列表和属性方法列表初始化
1 2 3 4 5 6 7 8 9 static struct _category_t _OBJC_$_CATEGORY_OneClass_$_OneCategory __attribute__ ((used, section ("__DATA,__objc_const" ))) = { "OneClass" , 0 , (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_OneClass_$_OneCategory, 0 , 0 , (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_OneClass_$_OneCategory, };
最后,编译器在DATA段下的objc_catlist section里保存了一个大小为1的category_t的数组L_OBJC_LABELCATEGORY$(当然,如果有多个category,会生成对应长度的数组),用于运行期category的加载。
1 2 3 static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1 ] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip" )))= { &_OBJC_$_CATEGORY_OneClass_$_OneCategory, };
Category运行期加载 objc_init 和 map_images 在OC运行时入口方法_objc_init
中((在objc-os.mm文件中)):
1 2 3 4 5 6 7 void _objc_init(void ){ …… _dyld_objc_notify_register(&map_images, load_images, unmap_image); …… }
具体点大概是这么个流程:
1 _objc_init -> map_images -> map_images_nolock -> _read_images
read_images _objc_init
里面的调用的map_images最终会调用objc-runtime-new.mm 里面的_read_images
方法,而在_read_images
方法的结尾,有以下的代码片段:
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 46 47 for (EACH_HEADER) { category_t **catlist = _getObjc2CategoryList(hi, &count); bool hasClassProperties = hi->info ()->hasCategoryClassProperties (); for (i = 0 ; i < count; i++) { category_t *cat = catlist[i]; Class cls = remapClass (cat->cls); if (!cls) { catlist[i] = nil; 。。。 continue ; } bool classExists = NO; if (cat->instanceMethods || cat->protocols || cat->instanceProperties) { addUnattachedCategoryForClass (cat, cls, hi); if (cls->isRealized ()) { remethodizeClass (cls); classExists = YES; } 。。。 } if (cat->classMethods || cat->protocols || (hasClassProperties && cat->_classProperties)) { addUnattachedCategoryForClass (cat, cls->ISA (), hi); if (cls->ISA ()->isRealized ()) { remethodizeClass (cls->ISA ()); } 。。。 } } }
从代码中可以看到remethodizeClass处理添加事宜方法
remethodizeClass && attachCategories 既然remethodizeClass才是真正去处理添加事宜方法那么现在看一下remethodizeClass:
1 2 3 4 5 6 7 8 9 10 static void remethodizeClass (Class cls) { category_list *cats; 。。。 if ((cats = unattachedCategoriesForClass (cls, false ))) { 。。。 attachCategories (cls, cats, true ); free (cats); } }
emmm 好像remethodizeClass也没干什么事,都交给了attachCategories
方法了嘛,接下来看下attachCategories:
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 46 47 48 49 50 51 52 53 54 55 56 static void attachCategories(Class cls, category_list *cats, bool flush_caches) { if (!cats) return; if (PrintReplacedMethods) printReplacements(cls, cats); bool isMeta = cls->isMetaClass(); // fixme rearrange to remove these intermediate allocations method_list_t **mlists = (method_list_t **) malloc(cats->count * sizeof(*mlists)); property_list_t **proplists = (property_list_t **) malloc(cats->count * sizeof(*proplists)); protocol_list_t **protolists = (protocol_list_t **) malloc(cats->count * sizeof(*protolists)); // Count backwards through cats to get newest categories first int mcount = 0; int propcount = 0; int protocount = 0; int i = cats->count; bool fromBundle = NO; while (i--) { auto& entry = cats->list[i]; method_list_t *mlist = entry.cat->methodsForMeta(isMeta); if (mlist) { mlists[mcount++] = mlist; fromBundle |= entry.hi->isBundle(); } property_list_t *proplist = entry.cat->propertiesForMeta(isMeta, entry.hi); if (proplist) { proplists[propcount++] = proplist; } protocol_list_t *protolist = entry.cat->protocols; if (protolist) { protolists[protocount++] = protolist; } } auto rw = cls->data(); prepareMethodLists(cls, mlists, mcount, NO, fromBundle); rw->methods.attachLists(mlists, mcount); free(mlists); if (flush_caches && mcount > 0) flushCaches(cls); rw->properties.attachLists(proplists, propcount); free(proplists); rw->protocols.attachLists(protolists, protocount); free(protolists); }
这个函数的主要作用是将 Category 中的方法、属性和协议整合到类(主类或元类)中,更新类的数据字段 data() 中 method_lists(或 method_list)、properties 和 protocols 的值。
category的方法没有替换掉原来类已经有的方法,也就是说如果category和原来类都有methodA,那么category附加完成之后,类的方法列表里会有两个methodA
category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面,这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会罢休。
怎么调用到原来类中被category覆盖掉的方法 ?对于这个问题,我们已经知道category其实并不是完全替换掉原来类的同名方法,只是category在方法列表的前面而已,所以我们只要顺着方法列表找到最后一个对应名字的方法,就可以调用原来类的方法