IOS开发

Category

2020-10-21  本文已影响0人  越天高

01-基本使用

Category的使用场合是什么?
为现有的类添加的新的方法,又不想改原来的类的时候

Category的实现原理
Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法、类方法、属性、协议信息
在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)

Category和Class Extension的区别是什么?
Class Extension在编译的时候,它的数据就已经包含在类信息中
Category是在运行时,才会将数据合并到类信息中

我们平时方法的调用就是发消息,他会根据isa根据找到类对象,去找方法,一个类只存在一个类对象,他的分类的方法存在于类对象之中,合并一起,如果是调的用是分类中类方法,他会的类方法存在于元类对象之中。

Category的地层结构

分类的合并并不是编译的时候,是在程序的运行的时候合并的,我们可以转成C++代码来查看,他首先会把分类的中的方法放到一个结构体里面,运行的时候才把他合并到类对象里面。
编译的时候 每一个分类都是在内存中转成下面这种结构体

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);
    
    protocol_list_t *protocolsForMeta(bool isMeta) {
        if (isMeta) return nullptr;
        else return protocols;
    }
};

03源码分析

运行时,他会将每一个分类的对象想法 合并到类对象中,将类方法合并到元类对象之中。
查看远嘛文件

objc-os.mm//运行的入口
objc_init//运行时的初始化
map_images //
map_images_nolock
objc_runtime_new.mm
 _read_images//镜像。模块.,他会读取所有的类信息,这里面会有一个Discover categories
重新组织方法,将分类的方法加到了原来的数组列表里面 
   remethodizeClass
    attachCategories
    attachLists 将所有分类方法附加到,类对象的方法列表中
    realloc、memmove、 memcpy

1通过Runtime加载某个类的所有Category数据

2把所有Category的方法、属性、协议数据,合并到一个大数组中
后面参与编译的Category数据,会在数组的前面

3将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面

04源码分析02

attachLists内部做的事情,他会从新分布内存,旧的内存加上新的内存

void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            // many lists -> many lists
//array()原来的方法列表
            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            array()->count = newCount;
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
//addedLists 所有分类的方法列表
            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]));
        }
    }

他会将原来的方法列表里面的数据,往后移动,然后将分类的方法列表数据添加到原来方法列表所在的地方。所以同样的方法,他会优先调用分类的方法,因为数组要进行两次,先添加到addlist里面,在添加到原来的方法类表里,所以最后编译的分类放到了,合并之后的方法列表的最前面。
分类的方法覆盖并不是真的覆盖,他只是放在了原来类对象的方法前面

Category和Class Extension的区别是什么?
@interface SLPerson()
@end
Class Extension在编译的时候,它的数据就已经包含在类信息中
Category是在运行时,才会将数据合并到类信息中
这里面的东西,编译的时候就合并到了类对象里面。分类是运行时在添加类对象里面

05-memmove、memcpy区别

memcpy 如果我们使用memcpy把两个数据往后移动一下,3 4 51,
他会先移动第一个3-> 3 3 5 1,然后在去移动第二位的数据-> 3 3 3 1。这样并不能达到我们想要的效果,
如果我们想要使用memmove他会根据内存的地址大小,来判断挪动的方向-> 3 3 4 5 。他可以保证原来的数据的完整性

06答疑

在类对象的结构体里面的class_rw_t里面有个ro表,它里面存在着类原来的数据包括baseMethodList,这里面就不包括分类的方法列表

上一篇下一篇

猜你喜欢

热点阅读