美文网首页iOS面试总结
OC语言之Category源码实现

OC语言之Category源码实现

作者: Jimmy_L_Wang | 来源:发表于2019-06-14 22:22 被阅读0次

    Category源码实现

    objc4-750.1版本中Category的定义如下:

    struct category_t {
        const char *name; //分类名
        classref_t cls;   //类
        struct method_list_t *instanceMethods; //实例方法列表
        struct method_list_t *classMethods;    //类方法列表
        struct protocol_list_t *protocols;      //遵守的协议列表
        struct property_list_t *instanceProperties; //实例属性列表
        // Fields below this point are not always present on disk.
        struct property_list_t *_classProperties; //类属性列表
    
        method_list_t *methodsForMeta(bool isMeta) {
            if (isMeta) return classMethods;
            else return instanceMethods;
        }
    
        property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
    };
    

    Category的加载流程

    在文件obj-os.mm文件中初始化

    void _objc_init(void)
    {
        static bool initialized = false;
        if (initialized) return;
        initialized = true;
        
        // fixme defer initialization until an objc-using image is found?
        environ_init();
        tls_init();
        static_init();
        lock_init();
        exception_init();
    
        _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    }
    

    category被附加到类上面是在map_images的时候发生的,在new-ABI的标准下,_objc_init里面的调用的map_images会调用objc-os.mm文件中的map_images_nolock ,然后调用objc-runtime-new.mm里面的_read_images方法,而在_read_images方法的结尾,有以下的代码片段:

    ...
      
    // Discover categories. 
        for (EACH_HEADER) {
            category_t **catlist = 
                _getObjc2CategoryList(hi, &count);
            bool hasClassProperties = hi->info()->hasCategoryClassProperties();
    
            for (i = 0; i < count; i++) {
                category_t *cat = catlist[i];
                Class cls = remapClass(cat->cls);
    
                if (!cls) {
                    // Category's target class is missing (probably weak-linked).
                    // Disavow any knowledge of this category.
                    catlist[i] = nil;
                    if (PrintConnecting) {
                        _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                     "missing weak-linked target class", 
                                     cat->name, cat);
                    }
                    continue;
                }
    
                // Process this category. 
                // First, register the category with its target class. 
                // Then, rebuild the class's method lists (etc) if 
                // the class is realized. 
                bool classExists = NO;
                if (cat->instanceMethods ||  cat->protocols  
                    ||  cat->instanceProperties) 
                {
                    addUnattachedCategoryForClass(cat, cls, hi);
                    if (cls->isRealized()) {
                        remethodizeClass(cls);
                        classExists = YES;
                    }
                    if (PrintConnecting) {
                        _objc_inform("CLASS: found category -%s(%s) %s", 
                                     cls->nameForLogging(), cat->name, 
                                     classExists ? "on existing class" : "");
                    }
                }
    
                if (cat->classMethods  ||  cat->protocols  
                    ||  (hasClassProperties && cat->_classProperties)) 
                {
                    addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                    if (cls->ISA()->isRealized()) {
                        remethodizeClass(cls->ISA());
                    }
                    if (PrintConnecting) {
                        _objc_inform("CLASS: found category +%s(%s)", 
                                     cls->nameForLogging(), cat->name);
                    }
                }
            }
        }
    
        ts.log("IMAGE TIMES: discover categories");
    
        // Category discovery MUST BE LAST to avoid potential races 
        // when other threads call the new category code before 
        // this thread finishes its fixups.
    

    这里做的就是:

    1. 把category的实例方法、协议以及属性添加到类上

    2. 把category的类方法和协议添加到类的metaclass

    最终调用remethodizeClass进行category的内容和逻辑加载:

    static void remethodizeClass(Class cls)
    {
        category_list *cats;
        bool isMeta;
    
        runtimeLock.assertLocked();
      /*
      我们只分析分类当中实例方法添加的逻辑,
      因此在这里我们假设`isMeta = NO`
      */
        isMeta = cls->isMetaClass(); //判断当前类是否为元类对象
    
        // Re-methodizing: check for more categories
      //获取cls中未完成整合的所有分类 cats意为分类的列表
        if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
            if (PrintConnecting) {
                _objc_inform("CLASS: attaching categories to class '%s' %s", 
                             cls->nameForLogging(), isMeta ? "(meta)" : "");
            }
            //将分类cats拼接到cls上
            attachCategories(cls, cats, true /*flush caches*/);        
            free(cats);
        }
    }
    

    然后调用函数attachCategories添加方法

    static void 
    attachCategories(Class cls, category_list *cats, bool flush_caches)
    {
        if (!cats) return;
        if (PrintReplacedMethods) printReplacements(cls, cats);
    
      /*
      我们只分析分类当中实例方法添加的逻辑,
      因此在这里我们假设`isMeta = NO`
      */
        bool isMeta = cls->isMetaClass();
    
        // fixme rearrange to remove these intermediate allocations
      //声明三个二维数组,方法列表,属性列表,协议列表
        method_list_t **mlists = (method_list_t **)
            malloc(cats->count * sizeof(*mlists));
        property_list_t **proplists = (property_list_t **)
            malloc(cats->count * sizeof(*proplists));
        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[mcount++] = mlist;
                fromBundle |= entry.hi->isBundle();
            }
    
          //属性列表添加规则,通方法列表添加规则
            property_list_t *proplist = 
                entry.cat->propertiesForMeta(isMeta, entry.hi);
            if (proplist) {
                proplists[propcount++] = proplist;
            }
    
          //协议列表添加规则 同方法列表添加规则
            protocol_list_t *protolist = entry.cat->protocols;
            if (protolist) {
                protolists[protocount++] = protolist;
            }
        }
    
      //获取宿主类当中的rw数据,其中包含宿主类的方法列表信息
        auto rw = cls->data();
    
      //主要针对 分类中有关于内存管理相关方法情况下的一些特殊处理
        prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
      /*
      rw代表宿主类
      methods代表类的方法列表
      attachLists方法的含义是将含有mcount个元素的mlists拼接到rw的methods上
      */
        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);
    }
    

    如果两个分类中都有一个同名的方法,那个方法最终会生效?

    答:最后编译的分类会生效。

    /*
    addedLists: 传递过来的二维数组(假设传过来的数组为3个数组列表,据此分析)
    [[method_t, method_t, ...], [method_t], [method_t, method_t, method_t, ...]]
    --------------------------  ----------  ------------------------------------
          分类A中的方法列表(A)        B                        C
    
    addedCount = 3;
    */
    void attachLists(List* const * addedLists, uint32_t addedCount) {
            if (addedCount == 0) return
    
            if (hasArray()) {
                // many lists -> many lists
              //列表中原有元素总数 假设oldCount = 2
                uint32_t oldCount = array()->count;
              //拼接之后的元素总数
                uint32_t newCount = oldCount + addedCount;
              //根据新总数重新分配内存
                setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
              //重新设置元素总数
                array()->count = newCount;
              /*
              内存移动
              [[], [], [], [原有的第一个元素], [原有的第二个元素]]
              */
                memmove(array()->lists + addedCount, array()->lists, 
                        oldCount * sizeof(array()->lists[0]));
              /*
              内存拷贝
              [
                  A      ----->     [addedLists中的第一个元素]
                  B      ----->     [addedLists中的第二个元素]
                  C      ----->     [addedLists中的第三个元素]
                  [原有的第一个元素]
                  [原有的第二个元素]
              ]
              这也是分类方法会“覆盖“宿主类的方法的原因
              */
                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]));
            }
        }
    

    为什么会造成宿主类中的方法出现被"覆盖“的假象?

    宿主类的方法仍然存在,由于在消息发送,或消息函数方法查找的过程中,是根据选择器名称进行查找的,一旦查找到对应的实现就会立即返回,由于分类方法位于数组顶上,如果说分类方法当中有和宿主类同名的方法的情况下,分类方法会优先实现。

    总结

    • 分类添加的方法可以"覆盖"原类方法
    • 同名分类方法谁能生效取决于编译顺序
    • 名字相同的分类会引起编译报错(因为在生成具体分类的过程中,经过runtime或编译的过程中,会把我们添加的分类名称,以下划线的形式添加到宿主类中)

    相关文章

      网友评论

        本文标题:OC语言之Category源码实现

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