美文网首页
iOS底层day4 - 探索Category的实现

iOS底层day4 - 探索Category的实现

作者: 宁夏灼雪__ | 来源:发表于2018-09-14 15:01 被阅读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寻找父类是否有这个方法,调用

    相关文章

      网友评论

          本文标题:iOS底层day4 - 探索Category的实现

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