美文网首页
Category 加载详解

Category 加载详解

作者: _一叶孤帆 | 来源:发表于2021-03-23 09:40 被阅读0次

    定义

    category 的主要作用是为已经存在的类添加方法。

    官方文档

    官网提供了其他的优势:

    • 可以把类的实现分开在几个不同的文件里面。
      • 可以减少分开文件的体积
      • 可以把不同的功能组织到不同的category里
      • 可以由多个开发者共同完成一个类
      • 可以按需加载想要的类别等等。
    • 声明专有方法

    原理

    思考?

    一个类的实例方法、类方法默认都是存在这个类的类对象和元类对象中,那么分类的方法存在哪呢?
    (也在类方法,合并,运行时合并 利用runtime)

    首先我们创建一个类,然后创建一个类的 category 文件,并使用编译命令来生成对应的 c++ 文件。

    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person+name.m
    

    可以看见我们的分类被编译成了下面这种结构

    // 空的分类
    static struct _category_t _OBJC_$_CATEGORY_Person_$_name __attribute__ ((used, section ("__DATA,__objc_const"))) = 
    {
        "Person",
        0, // &OBJC_CLASS_$_Person,
        0,
        0,
        0,
        0,
    };
    
    // 设置了方法 和 属性等 编译后的文件
    static struct _category_t _OBJC_$_CATEGORY_Person_$_name __attribute__ ((used, section ("__DATA,__objc_const"))) = 
    {
        "Person",
        0, // &OBJC_CLASS_$_Person,
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_name,
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_name,
        (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_name,
        (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_name,
    };
    

    结构体的类型为 _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; // 属性
    };
    

    实例方法的结构体

    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_$_name __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        1,
        {{(struct objc_selector *)"myName", "@16@0:8", (void *)_I_Person_name_myName}}
    };
    

    类方法的结构体

    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_$_name __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        1,
        {{(struct objc_selector *)"myHeight", "i16@0:8", (void *)_C_Person_name_myHeight}}
    };
    

    属性的结构体

    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_$_name __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_prop_t),
        1,
        {{"len","Ti,N"}}   // i  指的是 int 类型
    };
    
    

    协议的结构体

    static struct /*_protocol_list_t*/ {
        long protocol_count;  // Note, this is 32/64 bit
        struct _protocol_t *super_protocols[2];
    } _OBJC_CATEGORY_PROTOCOLS_$_Person_$_name __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        2,
        &_OBJC_PROTOCOL_NSCopying,
        &_OBJC_PROTOCOL_NSCoding
    };
    

    runtime 源码解读

    首先找到 runtime 的入口, 在 objc-os.mm 中的 _objc_init 函数

    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();
        runtime_init();
        exception_init();
    #if __OBJC2__
        cache_t::init();
    #endif
        _imp_implementationWithBlock_init();
    
        _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    
    #if __OBJC2__
        didCallDyldNotifyRegister = true;
    #endif
    }
    

    然后在 map_images 中调用 map_images_nolock,然后调用了_read_images
    然后里面有下面代码

    
        // Discover categories. Only do this after the initial category
        // attachment has been done. For categories present at startup,
        // discovery is deferred until the first load_images call after
        // the call to _dyld_objc_notify_register completes. rdar://problem/53119145
        if (didInitialAttachCategories) {
            for (EACH_HEADER) {
                load_categories_nolock(hi);
            }
        }
    

    跟进去之后,可以找到 attachCategories 方法,这部分代码就是合并分类的代码。

    // 附加上分类的核心操作
    // cls:类对象或者元类对象,cats_list:分类列表
    static void attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                     int flags)
    {
        if (slowpath(PrintReplacedMethods)) {
            printReplacements(cls, cats_list, cats_count);
        }
        if (slowpath(PrintConnecting)) {
            _objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
                         cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
                         cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
        }
    
        // 先分配固定内存空间来存放方法列表、属性列表和协议列表
        constexpr uint32_t ATTACH_BUFSIZ = 64;
        method_list_t   *mlists[ATTACH_BUFSIZ];
        property_list_t *proplists[ATTACH_BUFSIZ];
        protocol_list_t *protolists[ATTACH_BUFSIZ];
    
        uint32_t mcount = 0;
        uint32_t propcount = 0;
        uint32_t protocount = 0;
        bool fromBundle = NO;
        
        // 判断是否为元类
        bool isMeta = (flags & ATTACH_METACLASS);
        auto rwe = cls->data()->extAllocIfNeeded();
    
        for (uint32_t i = 0; i < cats_count; i++) {
            // 取出某个分类
            auto& entry = cats_list[i];
    
            // entry.cat就是category_t *cat
            // 根据isMeta属性取出每一个分类的类方法列表或者对象方法列表
            method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
            
            // 如果有方法则添加mlist数组到mlists这个大的方法数组中
            // mlists是一个二维数组:[[method_t, method_t, ....], [method_t, method_t, ....]]
            if (mlist) {
                if (mcount == ATTACH_BUFSIZ) {
                    prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
                    rwe->methods.attachLists(mlists, mcount);
                    mcount = 0;
                }
                // 将分类列表里先取出来的分类方法列表放到大数组mlists的最后面(ATTACH_BUFSIZ - ++mcount),所以最后编译的分类方法列表会放在整个方法列表大数组的最前面            
                mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
                fromBundle |= entry.hi->isBundle();
            }
    
            // 同上面一样取出的是分类中的属性列表proplist加到大数组proplists中
            // proplists是一个二维数组:[[property_t, property_t, ....], [property_t, property_t, ....]]
            property_list_t *proplist =
                entry.cat->propertiesForMeta(isMeta, entry.hi);
            if (proplist) {
                if (propcount == ATTACH_BUFSIZ) {
                    rwe->properties.attachLists(proplists, propcount);
                    propcount = 0;
                }
                proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
            }
    
            // 同上面一样取出的是分类中的协议列表protolist加到大数组protolists中
            // protolists是一个二维数组:[[protocol_ref_t, protocol_ref_t, ....], [protocol_ref_t, protocol_ref_t, ....]]
            protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
            if (protolist) {
                if (protocount == ATTACH_BUFSIZ) {
                    rwe->protocols.attachLists(protolists, protocount);
                    protocount = 0;
                }
                protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
            }
        }
    
        if (mcount > 0) {
            prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
                               NO, fromBundle, __func__);
            
            // 将分类的所有对象方法或者类方法,都附加到类对象或者元类对象的方法列表中
            rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
            if (flags & ATTACH_EXISTING) {
                flushCaches(cls, __func__, [](Class c){
                    return !c->cache.isConstantOptimizedCache();
                });
            }
        }
    
        // 将分类的所有属性附加到类对象的属性列表中
        rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
    
        // 将分类的所有协议附加到类对象的协议列表中
        rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
    }
    

    里面有关键性代码 attachLists, 在此处进行了分类方法的加载顺序。

    void attachLists(List* const * addedLists, uint32_t addedCount) {
       if (addedCount == 0) return;
    
       if (hasArray()) {
           // 这里是把类的地址重新分配新的地址加 newcount 也是分类的创建个数,而老的分类信息重新复制一下内存,通过 i-- 倒着取值,所以最先编译的会在最后面,
           // 获取原本的个数
           uint32_t oldCount = array()->count;
           // 最新的个数 = 原本的个数 + 新添加的个数
           uint32_t newCount = oldCount + addedCount;
           
           // 重新分配内存
           array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
           // 将新数组的个数改为最新的个数
           newArray->count = newCount;
           // 将旧数组的个数改为最新的个数
           array()->count = newCount;
           
           // 递减遍历,将旧数组里的元素从后往前的依次放到新数组里
           for (int i = oldCount - 1; i >= 0; i--)
               newArray->lists[i + addedCount] = array()->lists[i];
           
           // 将新增加的元素从前往后的依次放到新数组里
           for (unsigned i = 0; i < addedCount; i++)
               newArray->lists[i] = addedLists[i];
           
           // 释放旧数组数据
           free(array());
           
           // 赋值新数组数据
           setArray(newArray);
           validate();
       }
       else if (!list  &&  addedCount == 1) {
           // 0 lists -> 1 list
           list = addedLists[0];
           validate();
       } 
       else { .... }
    }
    

    上面内容就是分类方法的一个合并流程。

    参考文档:

    http://www.babyitellyou.com/details?id=6045c2fe4da5fa50e14ff4f3
    https://www.jianshu.com/p/ed8d3be7e8f4

    相关文章

      网友评论

          本文标题:Category 加载详解

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