美文网首页
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