美文网首页
Category的原理

Category的原理

作者: 昵称是乱起的 | 来源:发表于2019-01-21 12:53 被阅读22次
    分类做了哪些事情

    把一些体积庞大的类按功能拆解开放在分类中
    声明私有方法
    把Framework的私有方法公开
    分类添加的方法可以覆盖原来类中的方法,同名的分类方法谁能生效剩下取决于编译顺序,后编译的会覆盖前面的

    看一下分类的底层
    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);
    };
    

    发现分类的结构体里面没有成员变量的数组,这就是为什么分类不能添加成员变量的原因

    看一下分类的调用栈,看看分类是怎么合并的
    void _objc_init(void)  
    └──const char *map_2_images(...)
        └──const char *map_images_nolock(...)
            └──void _read_images(header_info **hList, uint32_t hCount)
                 └──static void remethodizeClass(Class cls)
                      └──static void attachCategories(Class cls, category_list *cats, bool flush_caches)
    
    void _objc_init(void)
    {
        //省略掉非关键代码
        _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    }
    void map_images(unsigned count, const char * const paths[],
               const struct mach_header * const mhdrs[])
    {
        rwlock_writer_t lock(runtimeLock);
        return map_images_nolock(count, paths, mhdrs);
    }
    map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                      const struct mach_header * const mhdrs[]) {
         //省略掉非关键代码
         _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
    void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
    {
         remethodizeClass(cls);
    }
    static void remethodizeClass(Class cls)
    {
        category_list *cats;
        bool isMeta;
        runtimeLock.assertWriting();
        isMeta = cls->isMetaClass();
        // Re-methodizing: check for more categories
        if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
            if (PrintConnecting) {
                _objc_inform("CLASS: attaching categories to class '%s' %s", 
                             cls->nameForLogging(), isMeta ? "(meta)" : "");
            }
            attachCategories(cls, cats, true /*flush caches*/);        
            free(cats);
        }
    }
    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
        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;
            }
        }
       auto rw = cls->data();
       repareMethodLists(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;
               //进行内存移动 把原来的数组里面的数据往后移动了addedCount个大小的位置
                memmove(array()->lists + addedCount, array()->lists, 
                        oldCount * sizeof(array()->lists[0]));
               //再把分类二维数组里面的方法列表copy到这个扩容的数组里面来
                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]));
            }
        }
    
    总结一下attachCategories代码思路,以合并实例方法为主

    创建一个二维数组 method_list_t **mlists = (method_list_t *) malloc(cats->count * sizeof(mlists))
    获得所有分类的数量int i = cats->count;
    倒叙遍历while (i--)(后来编译的会放在数组前面)
    获得i所对应的的实例方法列表数组method_list_t *mlist = entry.cat->methodsForMeta(NO);
    然后把method_list_t放在mlists二维数组里面,是直接把一维数组放在二维数组里面,也就是说不同分类之间的方法列表是分开的
    下面的类方法、协议和属性列表的合并是一样的
    根据(bits & FAST_DATA_MASK)获得class_rw_t
    然后class_rw_t得到里面的methods二维数组,进行数组合并rw->methods.attachLists(mlists, mcount);

    attachLists里面的思路

    oldCount是class_rw_t里面methods的数量
    newCount是oldCount加上分类的数量
    重新分配数组的大小setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
    再把分类二维数组里面的方法列表copy到这个扩容的数组里面来memcpy(array()->lists, addedLists, addedCount * sizeof(array()->lists[0]));

    其实这只是个大体思路而已,具体还有很多细节,Runtime源码中自己去看

    相关文章

      网友评论

          本文标题:Category的原理

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