美文网首页
聊一聊Category

聊一聊Category

作者: 晨阳Xia | 来源:发表于2020-12-24 16:46 被阅读0次

    Category的实现原理

    Category中对象方法,在程序运行过程中,都会进入类的对象方法列表中。实例变量都是在自己的类对象的方法列表中查找方法。
    Category中类方法,在程序运行过程中,都会进入元类的对象方法列表中。实例变量都是在自己的元类对象的方法列表中查找方法。

    Category 源码

    将Objective-C编译成C++代码的命令

    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person+Test.m -o Person+Test.cpp
    

    编译前:

    @interface Person (Test)<NSCopying>
    
    
    @property (nonatomic, strong) NSString *xsysString;
    
    - (void)test;
    
    @end
    
    @implementation Person (Test)
    
    - (void)run {
        NSLog(@"test");
    }
    
    + (void)test {
        
    }
    
    @end
    

    编译后:

    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;            // 属性
    };
    
    // 实例方法结构体
    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_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        1,
        {{(struct objc_selector *)"run", "v16@0:8", (void *)_I_Person_Test_run}}
    };
    
    // 类方法结构体
    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_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        1,
        {{(struct objc_selector *)"test", "v16@0:8", (void *)_C_Person_Test_test}}
    };
    
    // 协议
    static const char *_OBJC_PROTOCOL_METHOD_TYPES_NSCopying [] __attribute__ ((used, section ("__DATA,__objc_const"))) = 
    {
        "@24@0:8^{_NSZone=}16"
    };
    
    static struct /*_method_list_t*/ {
        unsigned int entsize;  // sizeof(struct _objc_method)
        unsigned int method_count;
        struct _objc_method method_list[1];
    } _OBJC_PROTOCOL_INSTANCE_METHODS_NSCopying __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        1,
        {{(struct objc_selector *)"copyWithZone:", "@24@0:8^{_NSZone=}16", 0}}
    };
    
    struct _protocol_t _OBJC_PROTOCOL_NSCopying __attribute__ ((used)) = {
        0,
        "NSCopying",
        0,
        (const struct method_list_t *)&_OBJC_PROTOCOL_INSTANCE_METHODS_NSCopying,
        0,
        0,
        0,
        0,
        sizeof(_protocol_t),
        0,
        (const char **)&_OBJC_PROTOCOL_METHOD_TYPES_NSCopying
    };
    struct _protocol_t *_OBJC_LABEL_PROTOCOL_$_NSCopying = &_OBJC_PROTOCOL_NSCopying;
    
    static struct /*_protocol_list_t*/ {
        long protocol_count;  // Note, this is 32/64 bit
        struct _protocol_t *super_protocols[1];
    } _OBJC_CATEGORY_PROTOCOLS_$_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        1,
        &_OBJC_PROTOCOL_NSCopying
    };
    
    // 属性
    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_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_prop_t),
        1,
        {{"xsysString","T@\"NSString\",&,N"}}
    };
    
    // 分类结构体赋值
    static struct _category_t _OBJC_$_CATEGORY_Person_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = 
    {
        "Person",                                                                               // 变量名赋值           
        0, // &OBJC_CLASS_$_Person,  
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Test,      // 实例方法结构体
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Test,         // 类方法结构体
        (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Test,           // 协议                                              
        (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_Test,                        // 属性
    };
    
    

    当程序一编译的时候,cagtegory中的数据会编译成_category_t结构体,数据存储在当前结构体内。

    runtime中Category源码解读

    过程分析:
    控制编译顺序:compile source中的文件顺序就是编译顺序
    最后面参与编译的分类会放在前面,同样的方法会优先调用分类的内的方法

    Category加载过程

    我试图追中源码,但是根据下面的过程,没有全部追踪到。可能是版本的问题。不过思路都是一样的,仅供参考


    image.png

    Category合并到类中的过程:
    运行时会通过remethodizeClass(class *cls)和 remethodizeClass(cls->ISA)方法将分类的信息分别合并到class对象和meta-class对象中。
    合并的步骤:
    1、类的分类按照编译顺序会存储在一个数组中category_list [_category_t,_category_t]
    2、倒序遍历分类的数组(这也是最后编译的分类生效的原因)。并将分类的方法列表,属性列表,协议列表存储在对应的二维数组中。

     // 二维数组
     method_list_t **mlists = ;              // 分类中的方法列表 
     property_list_t **proplists = ;         // 分类中的属性列表
     protocal_list_t **protlists = ;         // 分类中的协议列表
     i= category_list->count
     mcount = 0
     propcount = 0
     protcount = 0
    while(i--) { // 倒叙遍历分类
         category = category_list->list[i]
        
        /*
            [
                [method_t,method_t],
                [method_t,method_t]
            ]
        */
         method_list_t *mlist = category->methods
         if(mlist) {
              mlists[mcount ++] = mlist
         }
         
         /*
            [
                [property_t,property_t],
                [property_t,property_t]
            ]
        */
          property_list_t *proplist = category->porperities
         if(proplist) {
              proplists[propcount ++] = mlist
         }
         
         /*
            [
                [protocol_t,protocol_t],
                [protocol_t,protocol_t]
            ]
        */
         protocol_list_t *protlist = category->protocls
         if(protlist) {
              protlists[protcount ++] = mlist
         }
    }
    

    3、将方法列表协议列表属性列表合并到class对象或meta-class对象中

    rw->methods.attchLists(mlist,mcount);
    rw->properties.attchLists(proplist,propcount);
    rw->protocol.attchLists(protlist,protcount);
    
    

    4、attchLists过程:
    将原来的对象方法指针向后移动分类的个数的位置,在把空出的位置指向新的分类的方法列表。因为分类是倒序遍历,并将方法取出来存储在二维数组中的,所以最后编译的分裂的方法会生效。


    image.png

    所以同样的方法会优先调用分类的内的方法

    分类方法为什么会覆盖原来的方法?分类方法是真的覆盖原来的方法吗?

    会是原来的方法不生效。不是真正的覆盖,因为先找到了分类中的方法,不会再往后找而已。

    Category对象方法合并到Class对象方法列表中,Category的类方法合并到meta-class的类方法列表中

    Category与Extension的区别是什么

    分类实现原理:
    底层结构是 _category_t结构体,里面存储了分类的对象方法,类方法,属性信息,协议信息,在程序运行的时候,runtime会将Category里的数据合并到类信息中(类对象、元类对象中)
    Extension实现原理
    扩展在编译器的时候就已经合并到类里了

    区别:
    扩展在编译的时候数据就已经包含在类信息中,category在运行期才会将数据合并到类信息中。

    Category存在load方法吗

    相关文章

      网友评论

          本文标题:聊一聊Category

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