Objective - C 底层

Objective - C Category(一)定义及底层实现

2020-04-14  本文已影响0人  爱玩游戏的iOS菜鸟

Categoty要点

  1. Category的使用场合是什么?
  2. Category的实现原理
  3. Category和Class Extension的区别是什么?
  4. Category中有load方法吗?load方法是什么时候调用的?load 方法能继承吗?
  5. load、initialize方法的区别什么?它们在category中的调用的顺序?以及出现继承时他们之间的调用过程?
  6. Category能否添加成员变量?如果可以,如何给Category添加成员变量?

下面我们一条一条的来学习和理解Categoty主要的几点

(一)什么是分类(Category)? Category主要使用场合

  1. 什么是Category?
  1. Category主要使用场合

(二)Category的实现原理

在学习Category的实现原理之前,我们需要先知道Category的底层结构

(1)Category的底层结构
  1. Category的定义在objc-runtime-new.h文件中:
//重点属性已注解 其余可暂时忽略
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;//属性列表

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta) {
        if (isMeta) return nil; // classProperties;
        else return instanceProperties;
    }
};

另外也可通过将指定的分类编译生成.cpp文件,也可以查看。

  1. 如何在runtime源码中,查看category底层具体的实现原理呢?

源码解读顺序:
(1) objc-os.mm文件

(2) objc-runtime-new.mm文件

其中非常重要的方法attachCategories

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
    //定义3个数组
    //二维数组 [[method_t,method_t],[method_t],[method_t,method_t]]
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    //属性数组
    //二维数组 [[property_t,property_t],[property_t],[property_t,property_t]]
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    //协议数组
    //二维数组 [[protocol_t,protocol_t],[protocol_t],[protocol_t,protocol_t]]
    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);
        if (proplist) {
            proplists[propcount++] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }

    //得到类对象里面的数据(方法数组、属性数组、协议数组)
    auto rw = cls->data();
    prepareMethodLists(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);
}

接下来调用attachLists方法:

    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;
            //将原方法列表的内存往后挪动新增分类方法列表数量的位置 即将原方法放到数组最后面
            //具体分类添加方法的顺序,看编译顺序
            //[[category1_method_t,category1_method_t],[category2_method_t],[old_method_t,old_method_t]]
            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]));
        }
    }

因此,通过上面对runtime源码的分析,我们还可以看出:

  1. 如果分类方法与原方法同名,则分类方法优先调用(并没有“覆盖”,“掩盖”更恰当)
  2. 不同的分类方法同名,则看编译顺序,最后参与编译的优先级更高

编译顺序可以通过编译日志看出,可以在Build Phase -> Compile Sources 中文件的顺序更改

memcpy函数与memmove函数

上面添加方法时,用到了两个函数:memcpymemmove

  1. void *memcpy(void *__dst, const void *__src, size_t __n);
    说明:memcpy功能和memmove相同,但是memcpy中dest和source中的区域不能重叠,否则会出现未知结果。
  2. void *memmove(void *__dst, const void *__src, size_t __len);
    说明:memmove用于从source拷贝count个字符到dest,如果目标区域和源区域有重叠的话,memmove能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中

最后将Category的加载处理流程进行整理总结:

Category的加载处理流程
  1. 通过Runtime加载某个类的所有Category数据
  2. 把所有Category的方法、属性、协议数据,合并到一个新的大数组中
  3. 将合并后的Category数据(方法、属性、协议),插入到类原来数据的前面,并优先调用
Category的实现原理

Category在编译之后的底层结构是struct category_t ,存储着对象方法、类方法、协议、属性信息,在程序启动的runtime阶段,会将Category中的数据,合并到(类对象、元类对象)中

Category和Class Extenstion的区别

PS:重新走一遍分析流程,并将自己所用的分类进行整理,并思考其底层原理

上一篇下一篇

猜你喜欢

热点阅读