iOS底层day4 - 探索Category的实现

2018-09-14  本文已影响0人  宁夏灼雪__

什么是分类

把一个类拆分成多个模块,如需要把Person类分成AB模块,则分为Person+APerson+B模块,这就用到了分类,分类可以拓展类的属性、方法、协议等信息
如图:

0C59A38C-CC6A-48ED-B164-D672AFD432B1.png

分类的底层结构

objc-4的源码中,我们搜索category_t { 可以看到:

F8AADE08-7C7E-416D-8A96-69052AF1B324.png
category_t就是一个分类的结构体,而我们所创建的的一个分类其实就是一个category_t的结构体,category_t里面的结构跟类对象的结构很相似,包含了name(名称,类名),instanceMethods(对象方法)、classMethods(类方法)、protocols(协议)、属性等。
在编译的时候,分类的属性、方法、协议等会先存储在这个结构体里面,在运行的时候,使用runtime动态的把分类里面的方法、属性、协议等添加到类对象(元类对象)中,具体源码我们可以查看:
ECCAE7AB-BD89-49AE-A9BB-1AD80EDDCD99.png
最终可以找到这个方法 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
/* 方法数组[
 [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_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
/* 协议数组[
 [peotocol_t , peotocol_t],
[peotocol_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, entry.hi);
        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 将他们添加到类对象中。

Category和Class Extension 的区别

Class Extension :

@interface Person ()
@property (nonatomic,assign)int sex;
- (void)isBig;
@end

将属性、方法等封装在 .m文件里面,类似private(私有)的应用

这两者的区别:
Class Extension 在编译的时候,数据就已经包含类信息里了
Category是在运行时,通过runtime将数据合并到类信息中

+ Load 和 +initialize 方法的区别

Load:在runtime加载类、分类的时候根据函数地址直接调用,程序初始化就会调用,在Category中,先调用类的load(根据编译顺序),再调用分类的load(根据编译顺序)
initialize:在第一次接收到消息时调用,给类发送消息(objc_msgSend)才会调用,优先调用父类的initialize,再调用子类的initialize,且只会调用一次(父类的initialize可能会调用多次)

objc_msgSend() 方法实现

objc4源码中搜索objc_msgSend(发现这个方法是由汇编实现的

8F6F3D6E-C1BE-4715-A03D-9FCA5C4BCEBC.png
但是我们可以大概猜出他的实现思路:
一、由于initialize是第一次接受到消息调用,所以initialize的调用是在objc_msgSend方法里,所以他的调用顺序应该是在最前面,而且是只调用一次的判断
二、通过isa寻找类/元类对象,寻找方法调用
三、如果isa没有寻找到对应的方法,则通过superClass寻找父类是否有这个方法,调用
上一篇 下一篇

猜你喜欢

热点阅读