iOS category

category的实现原理

在上一篇文章iOS runtime中提到了class_rw_t这个结构,在category中的写的方法,协议,属性等会在程序运行经由runtime写入类的class_rw_t中。这是怎么处理的呢?

首先得看下category编译后的结构。新建一个NSObject的category printCategory.使用clang命令行编译出category的c++实现。

NSObject+printCategory.m文件如下

#import "NSObject+printCategory.h"


@implementation NSObject (printCategory)
+(void)printCategory{
    NSLog(@"category测试");
}
@end

clang命令行

clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk 分类的路径

得出NSObject+printCategory.cpp文件,cpp文件中代码较多,抽取其中的关键代码,我们编写的category代码最终帮我们生成了 struct _category_t类型的 _OBJC__CATEGORY_NSObject__printCategory的值

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_NSObject_$_printCategory __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"printCategory", "v16@0:8", (void *)_C_NSObject_printCategory_printCategory}}
};

static struct _category_t _OBJC_$_CATEGORY_NSObject_$_printCategory __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "NSObject",
    0, // &OBJC_CLASS_$_NSObject,
    0,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_NSObject_$_printCategory,
    0,
    0,
};

其中结构体_category_t的定义如下

struct _category_t {
    const char *name;//被添加分类的类名称
    struct _class_t *cls;
    const struct _method_list_t *instance_methods;//实例方法列表
    const struct _method_list_t *class_methods;//类方法列表
    const struct _protocol_list_t *protocols;//协议列表
    const struct _prop_list_t *properties;//属性列表
};

在编译过程中,编译器把category编译成上面的内容,然后运行时候经由runtime把_OBJC__CATEGORY_NSObject__printCategory里面的内容合并到类信息class_rw_t这个结构体中。

对于我们自定义的_OBJC__CATEGORY_NSObject__printCategory这个category,会把class_methods里面的值,也就是OBJC_printCategory结构题里面的方法列表,合并到类信息class_rw_t中,大概的流程图如下

QQ20200524-190258.png

class_rw_t 中的method_lists是一个二维数组,对于数组的每一行,存储一个文件里面的方法列表。如类的原有方法列表会存储到一个一维数组里面,printCategory这个category里面的方法会存储到另外一个一维数组里面。
当把分类的方法合并到method_lists里面时候,往method_lists里面插入一个分类的方法的数组。

runtime在合并category的方法列表的时候,会把类所有的category编译出来的方法数组加载出来,统计一共有多少个一维数组,假设一维数组的数量为n,然后把类原有的方法数组往后挪动n位。再把category里面的方法数组拷贝到method_lists数组里面。对于拷贝的顺序,越往后编译的category方法数组列表越靠前。

如下图中NSObject有两个category, printCategory 和 printCategory2。


[图片上传中...(QQ20200524-191721.png-b59d76-1590319063271-0)]

printCategory排在printCategory2前面,在method_lists列表中数组的顺序printCategory的方法数组列表比printCategory2的靠前。


QQ20200524-191721.png

当我们调用NSObject 的printCategory方法的时候,会去method_lists从前往后遍历查找方法。当在printCategory的分类方法数组中找到printCategory方法时,会停止查找方法,并把刚才找到的方法放到类的方法缓存中,并执行方法。所以即便printCategory2分类中也存在printCategory方法,也不会执行到。

如果分类中存在与类原有的相同的方法,分类中的方法会“覆盖”掉类原有的方法。如果有多个分类中存在同一个方法,调用时执行最后编译的分类方法。

你可能感兴趣的:(iOS category)