美文网首页
Category面试题(类和类别中普通方法,+load方法 ,+

Category面试题(类和类别中普通方法,+load方法 ,+

作者: 扶摇先生 | 来源:发表于2019-12-30 21:37 被阅读0次

    前言

    可怜身上衣正单,心忧炭贱愿天寒
    ----有感于各种强制解除劳动关系却仍有人为血汗工厂打工

    要了解这些方法的调用顺序,首先要了解category的底层结构和加载处理过程。

    category的底层结构和加载处理过程

    下图是category的底层结构 category的底层结构

    加载处理过程:
    通过Runtime加载某个类的所有Category数据
    把所有Category的方法、属性、协议数据,合并到一个大数组中
    后面参与编译的Category数据,会在数组的前面
    将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面
    然后category的普通方法是走的msg_Send方法,即通过isa指针和superclass指针查找方法列表,找到就直接执行,不再继续找。

    普通方法的调用顺序

    我们先说结论:当父类和子类和子类的类别同时实现某个方法test,当调用test方法时,他会走msg_send()消息转发机制,只调用最先找到的那个方法,查找顺序为分类->类->父类,如果两个分类实现同一个方法,那么先执行后编译的那个,也就是编译文件列表里下面那个;下面我们证明一下。
    我们首先创建一个RMAnimal 类,在里边声明一个+(void)test方法,然后创建RMPerson类继承自RMAnimal,再创建RMPerson+weight,RMPerson+Height分类,同时实现+(void)test

    @interface RMAnimal : NSObject
    + (void)test;
    @end
    @implementation RMAnimal
    + (void)test {
        NSLog(@"test ============RMAnimal");
    }
    @end
    @implementation RMPerson
    + (void)test {
        NSLog(@"test ============RMPerson");
    }
    @end
    
    @implementation RMPerson (weight)
    + (void)test {
        NSLog(@"test ============RMPerson (weight)");
    }
    @end
    
    @implementation RMPerson (Height)
    + (void)test {
        NSLog(@"test ============RMPerson (Height)");
    }
    @end
    

    执行代码 [RMPerson test];

    [RMPerson test];
    

    最终打印结果是

    test ============RMPerson (weight)
    

    此时的编译顺序如下:


    类别的编译顺序

    可以看到两个类别的编译顺序为RMPerson+Height,RMPerson+weight,只是执行了RMPerson+weight的test方法,这说明类别里的方法后编译的先被找到。那么为什么呢,因为在底层category最后将类别的方法,属性和协议与原来的类结合到一起的时候(执行attachCategories方法如下),取数组元素是执行一个while循环 倒序取的,所以在后边的方法,会被放到前边。

    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;
            }
        }
    
        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);
    }
    

    + (void)load方法的执行顺序

    1、如果所有的父类和子类以及父类的类别和子类类别都实现了load方法,所有的load方法都会被执行,且执行顺序为,父类->子类->所有的类别,当子类里有平级关系(比如class Man 和class Woman均继承自于Person),则按照Compile Sources 出现的顺序执行(上边的先执行),所有的类别有不分父类类别和子类类别,均按照Compile Sources 出现的顺序执行。
    2、当子类未实现 load 方法时,在加载该子类时,不会去调用其父类 load 方法。
    参考链接:iOS load方法调用机制解析

    + (void)initialize方法的执行顺序

    1、initialize 方法是在这个类第一次被使用到时才调用,具体为第一次调用该类的相关方法;
    2、父类的 initialize 先执行;
    3、如果子类没有实现 initialize,则会调用父类的initialize;
    4、如果子类实现了 initialize,那么就直接执行子类的 initialize
    5、理论上只会调用一次,但是因为采用了 objc_msgSend 来调用,所以如果子类没有实现 initialize,那么就会多次调用父类的 initialize,可以通过添加 if (self == [ClassName self]) 来进行判断;
    6、不像 load 方法,会区分类和分类保存在两个数组中分别执行, 分类的 initialize 方法会覆盖原来类的 initialize,且遵循分类的编译顺序原则,最靠后的分类最终替换掉之前的 initialize 方法;

    相关文章

      网友评论

          本文标题:Category面试题(类和类别中普通方法,+load方法 ,+

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