美文网首页
底层原理总结 — Category2

底层原理总结 — Category2

作者: 踩坑小分队 | 来源:发表于2020-02-08 21:27 被阅读0次

    底层原理总结 — Category2中我们看到了Category在编译的时候的结构

    思考以下几个问题:

    1、Category的原理是啥?
    2、当我们调用Category的方法是怎么调用的?
    3、当Category中和类中存在相同方法的时候,最终调用的是哪一个?
    4、当多个Category中存在相同方法的时候,最终调用的是哪一个?

    带着这几个问题我们看下objc的源码,来分析下流程。

    下载源码:https://opensource.apple.com/tarballs/objc4/
    顺着下面的方法直接找到最终目的地
    void _objc_init(void)
    map_images
    map_images_nolock
    _read_images 读取镜像
    remethodizeClass 重新组织下class和meta-class中的方法
    attachCategories 附加分类

    /**
     Class cls: 类对象[Person class]
     category_list *cats :分类 [Person (Category1),Person (Category2)]
     */
    static void 
    attachCategories(Class cls, category_list *cats, bool flush_caches)
    {
        if (!cats) return;
        if (PrintReplacedMethods) printReplacements(cls, cats);
    
        // 是否是元类对象
        bool isMeta = cls->isMetaClass();
    
        // fixme rearrange to remove these intermediate allocations
        
        /**方法数组
         [
            [Category2-方法1,Category2-方法2],
            [Category1-方法1,Category1-方法2]
         ]
         */
        method_list_t **mlists = (method_list_t **)
            malloc(cats->count * sizeof(*mlists));
        
        /**属性数组
        [
           [Category2-属性1,Category2-属性2],
           [Category1-属性1,Category1-属性2]
        ]
        */
        property_list_t **proplists = (property_list_t **)
            malloc(cats->count * sizeof(*proplists));
        
        /**协议数组
        [
           [Category2-协议1,Category2-协议2],
           [Category1-协议1,Category1-协议2]
        ]
        */
        protocol_list_t **protolists = (protocol_list_t **)
            malloc(cats->count * sizeof(*protolists));
    
        // Count backwards through cats to get newest categories first
        int mcount = 0;
        int propcount = 0;
        int protocount = 0;
        int i = cats->count;
        bool fromBundle = NO;
        
        // 倒叙遍历分类
        while (i--) {
            // 取出某个分类
            auto& entry = cats->list[i];
    
            // 获取分类的方法列表
            method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
            if (mlist) {
                // 将获取到的方法列表添加到 mlists 数组中
                mlists[mcount++] = mlist;
                fromBundle |= entry.hi->isBundle();
            }
            // 获取分类的属性列表
            property_list_t *proplist = 
                entry.cat->propertiesForMeta(isMeta, entry.hi);
            if (proplist) {
                // 将获取到的属性列表添加到 proplists 数组中
                proplists[propcount++] = proplist;
            }
            // 获取分类的协议列表
            protocol_list_t *protolist = entry.cat->protocols;
            if (protolist) {
                protolists[protocount++] = protolist;
            }
        }
    
        // 获取类对象中的数据
        auto rw = cls->data();
    
        
        prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
        // 将分类中的所有的对象方法,附加到类对象中
        rw->methods.attachLists(mlists, mcount);
        free(mlists);
        if (flush_caches  &&  mcount > 0) flushCaches(cls);
    
        // 将分类中的所有的属性,附加到类对象中
        rw->properties.attachLists(proplists, propcount);
        free(proplists);
    
        // 将分类中的所有的协议,附加到类对象中
        rw->protocols.attachLists(protolists, protocount);
        free(protolists);
    }
    
            void attachLists(List* const * addedLists, uint32_t addedCount) {
            if (addedCount == 0) return;
    
            if (hasArray()) {
                // many lists -> many lists
                // 旧的列表个数
                uint32_t oldCount = array()->count;
                // 新的个数 = 将要添加+旧的列表个数
                uint32_t newCount = oldCount + addedCount;
                // 重新分配内存大小
                setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
                array()->count = newCount;
                
                // 将 array()->lists 的 oldCount * sizeof(array()->lists[0])个字符挪动到 array()->lists + addedCount 位置
                
                // 换句话说,就是把之前的列表的位置移动到最新扩容列表的最后
                memmove(array()->lists + addedCount,
                        array()->lists,
                        oldCount * sizeof(array()->lists[0]));
                
                // 将所有分类列表,挪动到class的列表中去
                memcpy(array()->lists, addedLists, 
                       addedCount * sizeof(array()->lists[0]));
            }
            else if (!list  &&  addedCount == 1) {
                // 0 lists -> 1 list
                list = addedLists[0];
            } 
            else {
                // 1 list -> many lists
                List* oldList = list;
                uint32_t oldCount = oldList ? 1 : 0;
                uint32_t newCount = oldCount + addedCount;
                setArray((array_t *)malloc(array_t::byteSize(newCount)));
                array()->count = newCount;
                if (oldList) array()->lists[addedCount] = oldList;
                memcpy(array()->lists, addedLists, 
                       addedCount * sizeof(array()->lists[0]));
            }
        }
    

    最终总结流程图


    分类的方法合并.png

    我们可以通过runtime的方法查看下Person类中的相关方法,可以看到最终的类中的方法是,类+分类的方法的合并。

    /// 获取类对象中的方法
    /// @param class class
    - (void)getAllMethodList:(Class)class {
        unsigned int count = 0;
        Method *methodList = class_copyMethodList(class, &count);
        for (NSInteger i = 0; i < count; i++) {
            Method subMethod = methodList[i];
            NSLog(@"%@",NSStringFromSelector(method_getName(subMethod)));
        }
    }
    
    NSLog(@"Person ------ 所有的对象方法");
    [self getAllMethodList:[Person class]];
    
    查看Person中所有的对象方法.png
    NSLog(@"Person ------ 所有的类方法");
    [self getAllMethodList:object_getClass([Person class])];
    
    查看Person中所有的类方法.png

    总结

    1、Category的原理是啥?

    Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法,类方法,协议,属性信息。
    在程序运行的时候,runtime会将Category的数据合并到类中<类对象和元类对象>

    2、当我们调用Category的方法是怎么调用的?

    参考第一条问题答案。
    调用对象方法:通过对象的isa找到类对象,找到类对象中的方法,调用
    调用类方法:通过类对象的isa找到元类对象,扎到元类对象中的类方法,调用

    3、当Category中和类中存在相同方法的时候,最终调用的是哪一个?

    调用的是Category中的方法
    因为类方法列表已经放到整体方法列表的后面了。
    所以调用相同方法的时候,会优先遍历到分类中的方法

    image.png
    4、当多个Category中存在相同方法的时候,最终调用的是哪一个?

    会调用后编译的分类中的方法
    因为runtime将分类中的方法列表合并到类对象中的时候,是倒叙遍历的,所以后被编译的分类中的方法列表优先被加到类对象中。


    image.png image.png

    那么分类的编译顺序在哪里查看呢?


    image.png

    相关文章

      网友评论

          本文标题:底层原理总结 — Category2

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