iOS

Category实现的原理一:底层结构及源码分析

2019-03-05  本文已影响18人  小心韩国人

分类在我们的项目中经常被使用到,它是给现有的类添加方法,也可用于根据功能划分模块,今天我们就来研究一下分类实现的原理.

❓比如说有如下3个类,思考一下如下图所示的实例方法存放在哪里?


我们之前研究OC对象的本质时已经知道了,实例方法存放在类对象中,类方法存放在元类对象中,也就是说- thought:方法存放在HHPerson类对象中.那么分类中的方法存放在哪里呢?莫非-eat:方法存放在HHPerson+eat类对象中,-run:方法存放在HHPerson+run类对象中?
事实上,一个类在内存中只存在一个类对象,-thought:,-eat:,-run:这三个实例方法在编译期都存放在struct _category_t这个结构体中,等到运行期利用runtime机制动态合并到HHPerson这个类对象中.

下面我们就来验证一下上面的结论.

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;//属性列表
};

我们的分类HHPerson+eat被转换为类型为static struct _category_t,变量名为:_OBJC_$_CATEGORY_HHPerson_$_eat:

static struct _category_t _OBJC_$_CATEGORY_HHPerson_$_eat __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "HHPerson",
    0, // &OBJC_CLASS_$_HHPerson,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_HHPerson_$_eat,
    0,
    (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_HHPerson_$_eat,
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_HHPerson_$_eat,
};
对比图

实例方法列表:

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

协议列表: (HHPerson+eat实现了NSCopying,NSCoding协议)

static struct /*_protocol_list_t*/ {
    long protocol_count;  // Note, this is 32/64 bit
    struct _protocol_t *super_protocols[2];
} _OBJC_CATEGORY_PROTOCOLS_$_HHPerson_$_eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    2,
    &_OBJC_PROTOCOL_NSCopying,
    &_OBJC_PROTOCOL_NSCoding
};

属性列表:

static struct /*_prop_list_t*/ {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count_of_properties;
    struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_HHPerson_$_eat __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    1,
    {{"age","Ti,N"}}
};

以上就是分类的底层结构,可以看到,分类的信息在编译期间都被分离出来了,下面我们从runtime源码研究一下分类.

  1. 搜索并打开objc-os.mm源文件,找到void _objc_init(void)方法.此方法是Runtime初始化入口.
  2. 点击进入map_images:
  3. 点击进入map_images_nolock:
  4. 点击进入_read_images(现在开始已经进入加载模块):
  5. _read_images中找到// Discover categories.(搜索分类),我们重点研究这里:
    检索分类
  1. 点击进入remethodizeClass(),找到attachCategories(cls, cats, true /*flush caches*/);
    附加分类
  2. 点击进入attachCategories(cls, cats, true /*flush caches*/);:
    attachCategories方法内部逻辑

我们梳理一下attachCategories (cls,cats,true)方法.attach一词是附加的意思,从名字上我们可以看出这个方法大概意思是:附加分类.事实上它的确如此,下面我开始研究:

  1. 参数分析:
  1. 首先分配内存,创建三个二维数组mlists,proplists,protolists分别用来存放方法列表,属性列表,协议列表

  2. 通过while循环遍历分类列表cats,取出某一个分类.

  3. 未完待续...

上一篇下一篇

猜你喜欢

热点阅读