objc category的秘密

来源:互联网 发布:软件研发经理岗位职责 编辑:程序博客网 时间:2024/04/30 10:25

转自sunnyxx的技术博客

 
category的真面目
objc所有类和对象都是c结构体,category当然也一样,下面是runtime中category的结构:
  1. struct _category_t { 
  2.     const char *name; // 1 
  3.     struct _class_t *cls; // 2 
  4.     const struct _method_list_t *instance_methods; // 3 
  5.     const struct _method_list_t *class_methods; // 4 
  6.     const struct _protocol_list_t *protocols; // 5 
  7.     const struct _prop_list_t *properties; // 6 
  8. }; 
1、注意name,并不是category小括号里写的名字,而是类的名字
 
2、cls要扩展的类对象,编译期间这个值是不会有的,在app被runtime加载时才会根据name对应到类对象
 
3、instance_methods这个category所有的-方法
 
4、class_methods这个category所有的+方法
 
5、protocols这个category实现的protocol,比较不常用在category里面实现协议,但是确实支持的
 
6、properties这个category所有的property,这也是category里面可以定义属性的原因,不过这个property不会@synthesize实例变量,一般有需求添加实例变量属性时会采用objc_setAssociatedObject和objc_getAssociatedObject方法绑定方法绑定,不过这种方法生成的与一个普通的实例变量完全是两码事。
 
编译器,你对category干了什么?
举个例子看,定义下面一个类和它的category,实现忽略,保存为sark.h和sark.m
  1. @interface Sark : NSObject 
  2. - (void)speak; 
  3. @end 
  4.  
  5. @interface Sark (GayExtention) 
  6. - (void)burst; 
  7. @end 
请出clang的重写命令:
  1. $ clang -rewrite-objc sark.m 
同级目录下会生成sark.cpp,这就是objc代码重写成c++(基本就是c)的实现。
 
打开生成的文件,发现茫茫多,排除include进来的header,自己的代码都在文件尾部了,看看上面的category被编译器搞成什么样子了:
  1. static struct _category_t _OBJC_$_CATEGORY_Sark_$_GayExtention __attribute__ ((used, section ("__DATA,__objc_const"))) = 
  2.     "Sark"
  3.     0, // &OBJC_CLASS_$_Sark, 
  4.     (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Sark_$_GayExtention, 
  5.     0, 
  6.     0, 
  7.     0, 
  8. }; 
先注意这个category的名字_OBJC_$_CATEGORY_Sark_$_GayExtention,这是一个按规则生成的符号了,中间的Sark是类名,后面的GayExtention是类别的名字,这也就是为什么同一个类的category名不能冲突了
 
对应看上面_category_t的定义,因为category里面只添加了一个- burst方法,所以只有实例方法那一项被填充了值_OBJC_$_CATEGORY_INSTANCE_METHODS_Sark_$_GayExtention
 
其中_I_Sark_GayExtention_burst符号就代表了category里面的- burst方法,同样遵循了一定的命名规范,里面的I表示实例方法
 
最后,这个类的category们生成了一个数组,存在了__DATA段下的__objc_catlistsection里
  1. static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= { 
  2.     &_OBJC_$_CATEGORY_Sark_$_GayExtention, 
  3. }; 
至此编译器的任务完成了。
 
runtime,我的category哪儿去了?
我们知道,category动态扩展了原来类的方法,在调用者看来好像原来类本来就有这些方法似的,有两个事实:
 
1、不论有没有import category 的.h,都可以成功调用category的方法,都影响不到category的加载流程,import只是帮助了编译检查和链接过程
 
2、runtime加载完成后,category的原始信息在类结构里将不会存在
 
这需要探究下runtime对category的加载过程,这里就简单说一下
 
1、objc runtime的加载入口是一个叫_objc_init的方法,在library加载前由libSystem dyld调用,进行初始化操作
2、调用map_images方法将文件中的imagemap到内存
3、调用_read_images方法初始化map后的image,这里面干了很多的事情,像load所有的类、协议和category,著名的+ load方法就是这一步调用的
4、仔细看category的初始化,循环调用了_getObjc2CategoryList方法,这个方法拿出来看看:
5、…
 
  1. #define GETSECT(name, type, sectname)                                   \ 
  2.     type *name(const header_info *hi, size_t *outCount)  \ 
  3.     {                                                                   \ 
  4.         unsigned long byteCount = 0;                                    \ 
  5.         type *data = (type *)                                           \ 
  6.             getsectiondata(hi->mhdr, SEG_DATA, sectname, &byteCount);   \ 
  7.         *outCount = byteCount / sizeof(type);                           \ 
  8.         return data;                                                    \ 
  9.     } 
  10.  
  11. // ... // 
  12.  
  13. GETSECT(_getObjc2CategoryList, category_t *, "__objc_catlist"); 
眼熟的__objc_catlist,就是上面category存放的数据段了,可以串连起来了
 
在调用完_getObjc2CategoryList后,runtime终于开始了category的处理,简化的代码如下
  1. // Process this category. 
  2. // First, register the category with its target class. 
  3. // Then, rebuild the class's method lists (etc) if 
  4. // the class is realized. 
  5. BOOL classExists = NO; 
  6. if (cat->instanceMethods ||  cat->protocols  ||  cat->instanceProperties) 
  7.     addUnattachedCategoryForClass(cat, cls, hi); 
  8.     if (isRealized(cls)) { 
  9.         remethodizeClass(cls); 
  10.         classExists = YES; 
  11.     } 
  12.  
  13. if (cat->classMethods  ||  cat->protocols ) 
  14.     addUnattachedCategoryForClass(cat, cls->isa, hi); 
  15.     if (isRealized(cls->isa)) { 
  16.         remethodizeClass(cls->isa); 
  17.     } 
首先分成两拨,一拨是实例对象相关的调用addUnattachedCategoryForClass,一拨是类对象相关的调用addUnattachedCategoryForClass,然后会调到attachCategoryMethods方法,这个方法把一个类所有的category_list的所有方法取出来组成一个method_list_t **,注意,这里是倒序添加的,也就是说,新生成的category的方法会先于旧的category的方法插入
  1. static void 
  2. attachCategoryMethods(class_t *cls, category_list *cats, 
  3.                       BOOL *inoutVtablesAffected) 
  4.     if (!cats) return
  5.     if (PrintReplacedMethods) printReplacements(cls, cats); 
  6.  
  7.     BOOL isMeta = isMetaClass(cls); 
  8.     method_list_t **mlists = (method_list_t **) 
  9.         _malloc_internal(cats->count * sizeof(*mlists)); 
  10.  
  11.     // Count backwards through cats to get newest categories first 
  12.     int mcount = 0; 
  13.     int i = cats->count; 
  14.     BOOL fromBundle = NO; 
  15.     while (i--) { 
  16.         method_list_t *mlist = cat_method_list(cats->list[i].cat, isMeta); 
  17.         if (mlist) { 
  18.             mlists[mcount++] = mlist; 
  19.             fromBundle |= cats->list[i].fromBundle; 
  20.         } 
  21.     } 
  22.  
  23.     attachMethodLists(cls, mlists, mcount, NO, fromBundle, inoutVtablesAffected); 
  24.  
  25.     _free_internal(mlists); 
  26.  
生成了所有method的list之后,调用attachMethodLists将所有方法前序添加进类的方法的数组中,也就是说,如果原来类的方法是a,b,c,类别的方法是1,2,3,那么插入之后的方法将会是1,2,3,a,b,c,也就是说,原来类的方法被category的方法覆盖了,但被覆盖的方法确实还在那里。
0 0
原创粉丝点击