美文网首页iOS
Category实现的原理一:底层结构及源码分析

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

作者: 小心韩国人 | 来源:发表于2019-03-05 16:06 被阅读18次

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

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


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

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

    • 一:窥探Category 底层数据结构
      我们写一个分类HHPerson+eat,然后转换成.cpp文件,发现Category底层被转换为struct _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;//属性列表
    };
    

    我们的分类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源码研究一下分类.

    • 二:从Runtime源码查看Category
      打开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.(搜索分类),我们重点研究这里:
      检索分类
    • category_t **catlist 是一个二位数组,里面存放的使我们给一个类创建的所有分类,比如上面我们给HHPerson类添加了三个分类HHPerson +eat,HHPerson +run,HHPerson +thought就存放在这个二维数组中:[[category_t_eat],[category_t_run],[category_t_thought]].
    • remethodizeClass();重新组织方法,传入类对象就是重新组织实例方法,传入cls->ISA()就是重新组织类方法.
    1. 点击进入remethodizeClass(),找到attachCategories(cls, cats, true /*flush caches*/);
      附加分类
    2. 点击进入attachCategories(cls, cats, true /*flush caches*/);:
      attachCategories方法内部逻辑

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

    1. 参数分析:
    • cls , 分类的本类,就是我们现在HHPerson
    • cats , 分类列表,[category_t (HHPerson+run),category_t (HHPerson+eat), category_t (HHPerson+thought)].
    1. 首先分配内存,创建三个二维数组mlists,proplists,protolists分别用来存放方法列表,属性列表,协议列表

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

    3. 未完待续...

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/pxjnuqtx.html