objc category的秘密
2016-05-14 21:49
549 查看
category的真面目
objc所有类和对象都是c结构体,category当然也一样,下面是runtime中category的结构:
struct _category_t { const char *name; // 1 struct _class_t *cls; // 2 const struct _method_list_t *instance_methods; // 3 const struct _method_list_t *class_methods; // 4 const struct _protocol_list_t *protocols; // 5 const struct _prop_list_t *properties; // 6 }; |
name注意,并不是category小括号里写的名字,而是类的名字
cls要扩展的类对象,编译期间这个值是不会有的,在app被runtime加载时才会根据
name对应到类对象
instance_methods这个category所有的
-方法
class_methods这个category所有的
+方法
protocols这个category实现的protocol,比较不常用在category里面实现协议,但是确实支持的
properties这个category所有的property,这也是category里面可以定义属性的原因,不过这个property不会
@synthesize实例变量,一般有需求添加实例变量属性时会采用
objc_setAssociatedObject和
objc_getAssociatedObject方法绑定方法绑定,不过这种方法生成的与一个普通的实例变量完全是两码事。
编译器,你对category干了什么?
举个栗子看,定义下面一个类和它的category,实现忽略,保存为sark.h和
sark.m
@interface Sark : NSObject - (void)speak; @end @interface Sark (GayExtention) - (void)burst; @end |
$ clang -rewrite-objc sark.m |
sark.cpp,这就是
objc代码重写成
c++(基本就是c)的实现。
打开生成的文件,发现茫茫多,排除include进来的header,自己的代码都在文件尾部了,看看上面的category被编译器搞成什么样子了:
static struct _category_t _OBJC_$_CATEGORY_Sark_$_GayExtention __attribute__ ((used, section ("__DATA,__objc_const"))) = { "Sark", 0, // &OBJC_CLASS_$_Sark, (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Sark_$_GayExtention, 0, 0, 0 }; |
_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里
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= { &_OBJC_$_CATEGORY_Sark_$_GayExtention, }; |
runtime,我的category哪儿去了?
我们知道,category动态扩展了原来类的方法,在调用者看来好像原来类本来就有这些方法似的,有两个事实:不论有没有import category 的
.h,都可以成功调用category的方法,都影响不到category的加载流程,import只是帮助了编译检查和链接过程
runtime加载完成后,category的原始信息在类结构里将不会存在
这需要探究下runtime对category的加载过程,这里就简单说一下
objc runtime的加载入口是一个叫
_objc_init的方法,在library加载前由libSystem
dyld调用,进行初始化操作
调用
map_images方法将文件中的
imagemap到内存
调用
_read_images方法初始化map后的
image,这里面干了很多的事情,像load所有的类、协议和category,著名的
+ load方法就是这一步调用的
仔细看category的初始化,循环调用了
_getObjc2CategoryList方法,这个方法拿出来看看:
…
#define GETSECT(name, type, sectname) \ type *name(const header_info *hi, size_t *outCount) \ { \ unsigned long byteCount = 0; \ type *data = (type *) \ getsectiondata(hi->mhdr, SEG_DATA, sectname, &byteCount); \ *outCount = byteCount / sizeof(type); \ return data; \ } // ... // GETSECT(_getObjc2CategoryList, category_t *, "__objc_catlist"); |
__objc_catlist,就是上面category存放的数据段了,可以串连起来了
在调用完
_getObjc2CategoryList后,runtime终于开始了category的处理,简化的代码如下
// Process this category. // First, register the category with its target class. // Then, rebuild the class's method lists (etc) if // the class is realized. BOOL classExists = NO; if (cat->instanceMethods || cat->protocols || cat->instanceProperties) { addUnattachedCategoryForClass(cat, cls, hi); if (isRealized(cls)) { remethodizeClass(cls); classExists = YES; } } if (cat->classMethods || cat->protocols ) { addUnattachedCategoryForClass(cat, cls->isa, hi); if (isRealized(cls->isa)) { remethodizeClass(cls->isa); } } |
addUnattachedCategoryForClass,一拨是类对象相关的调用
addUnattachedCategoryForClass,然后会调到
attachCategoryMethods方法,这个方法把一个类所有的category_list的所有方法取出来组成一个
method_list_t **,注意,这里是
倒序添加的,也就是说,新生成的category的方法会先于旧的category的方法插入
static void attachCategoryMethods(class_t *cls, category_list *cats, BOOL *inoutVtablesAffected) { if (!cats) return; if (PrintReplacedMethods) printReplacements(cls, cats); BOOL isMeta = isMetaClass(cls); method_list_t **mlists = (method_list_t **) _malloc_internal(cats->count * sizeof(*mlists)); // Count backwards through cats to get newest categories first int mcount = 0; int i = cats->count; BOOL fromBundle = NO; while (i--) { method_list_t *mlist = cat_method_list(cats->list[i].cat, isMeta); if (mlist) { mlists[mcount++] = mlist; fromBundle |= cats->list[i].fromBundle; } } attachMethodLists(cls, mlists, mcount, NO, fromBundle, inoutVtablesAffected); _free_internal(mlists); } |
attachMethodLists将所有方法
前序添加进类的方法的数组中,也就是说,如果原来类的方法是a,b,c,类别的方法是1,2,3,那么插入之后的方法将会是1,2,3,a,b,c,也就是说,原来类的方法被category的方法
覆盖了,但被覆盖的方法确实还在那里。
相关文章推荐
- google hacking搜索语法总结
- GOICE项目初探
- golang slice 切片原理
- 安装pillow
- 【GDOI2016Day2】第一题SigemaGO
- golang中一些基础用法
- 脑洞君来了,Google 2016科学挑战赛即将举行
- Bug:“iterator_category”: 不是*的成员——当自定义函数与系统函数重名
- How to solve this problem oC
- 【GDOI 2016 Day2】第一题 SigemaGO
- 关于django服务器重启端口占用解决方案
- Google谷歌在根据流量统计分析当年的2008年汶川大地震
- 下载Google地图,瓦片数据和经纬坐标的切换
- hdu 3635 Dragon Balls
- 【GDOI 2016 Day2】第一题 SigemaGO
- UVALive 11383 - Golden Tiger Claw(最小权匹配)
- Django基础
- go邮件列表问题记录1
- 一篇文章带你读懂 Google 在 AR/VR 领域的布局
- Go语言学习(十五)面向对象编程-接口