美文网首页
Objective-C:Category、Extension

Objective-C:Category、Extension

作者: 打碟的DJ | 来源:发表于2019-10-26 23:46 被阅读0次

    Category

    分类可以做哪些事情

    • 申明私有方法
    • 分解体积庞大的类文件
    • 将framework方法私有化

    特点

    • 运行时决议
    • 为系统类添加分类

    可以添加的内容

    • 实例方法
    • 类方法
    • 协议
    • 属性(实例属性、类属性)

    分类的结构体

    /**
     * 分类结构体
     * @by Si
     */
    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);
    };
    

    分类的加载调用栈

    分类加载调用栈.png

    分类源码分析

    static void remethodizeClass(Class cls)
    {
        category_list *cats;
        bool isMeta;
    
        // runtimeLock 需要事先加锁
        runtimeLock.assertLocked();
    
        /*
         当前类是否是元类
         */
        isMeta = cls->isMetaClass();
    
        // Re-methodizing: check for more categories
        // 获取cls中为整合的所有分类,也就是我们创建的Category
        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);
        }
    }
    
    // Attach method lists and properties and protocols from categories to a class.
    // Assumes the categories in cats are all loaded and sorted by load order, 
    // oldest categories first.
    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;
            }
        }
    
        // 获取宿主类中的rw数据,其中包含了宿主类的方法列表信息
        auto rw = cls->data();
    
        // 主要是针对 分类中有关于内存管理相关方法 的一些特殊处理
        prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
        
        /*
         rw:类
         methods:类的方法列表
         attachLists:将含有mcout个元素的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);
    }
    
        /**
         * 拼接类的方法列表
         * @param addedLists 传递过来的二维数组
         * @param addedCount 长度
         * @by Si
         */
        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;
                /**
                 内存移动
                 [[新增的第一个元素], ... [新增的第n个元素], [原有的第一个元素] ... [原有的第n个元素]]
                 */
                memmove(array()->lists + addedCount, array()->lists,
                        oldCount * sizeof(array()->lists[0]));
                /**
                 内存拷贝
                 分类方法会"覆盖"宿主类的方法的原因:消息函数查找方法时是根据选择器名称来选择的,查找到就会返回
                 */
                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]));
            }
        }
    

    总结

    • 分类添加的方法会 "覆盖" 宿主类的方法
    • 同名分类方法谁能生效取决于编译顺序
    • 名字相同的分类会引起编译错误

    Extension

    能做什么

    • 申明私有属性
    • 申明私有方法
    • 申明私有成员变量

    特点

    • 编译时决定
    • 只以申明的形式决定,大多数存在.m中
    • 不可以为系统类添加扩展
    tips: 当多个分类方法重名时,最后编译的分类中的方法会被执行
    编译顺序图.png

    相关文章

      网友评论

          本文标题:Objective-C:Category、Extension

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