美文网首页
OC基础-category(2)

OC基础-category(2)

作者: 我是卖报的小行家 | 来源:发表于2021-03-04 10:41 被阅读0次

    Category的load方法

    定义一个ZKPerson类,然后类扩展出来两个分类(Test)(Test1)如下图

    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface ZKPerson : NSObject
    +(void)test;
    @end
    
    #import "ZKPerson.h"
    
    @implementation ZKPerson
    + (void)load
    {
        NSLog(@"ZKPerson - +load");
    }
    + (void)test
    {
        NSLog(@"ZKPerson - +test");
    }
    @end
    

    Test

    #import "ZKPerson.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface ZKPerson (Test)
    +(void)test;
    @end
    
    NS_ASSUME_NONNULL_END
    
    #import "ZKPerson+Test.h"
    
    @implementation ZKPerson (Test)
    + (void)load
    {
        NSLog(@"ZKPerson (Test)- +load");
    }
    + (void)test //这个警告的意思是, 在category中重写了原类的方法
    {
        NSLog(@"ZKPerson(Test) - +test");
    }
    @end
    

    Test1

    
    #import "ZKPerson.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface ZKPerson (Test1)
    +(void)test;
    @end
    
    NS_ASSUME_NONNULL_END
    
    #import "ZKPerson+Test1.h"
    @implementation ZKPerson (Test1)
    + (void)load
    {
        NSLog(@"ZKPerson (Test1)- +load");
    }
    + (void)test //这个警告的意思是, 在category中重写了原类的方法
    {
        NSLog(@"ZKPerson(Test1) - +test");
    }
    @end
    

    调用 [ZKPerson test];查看打印结果如下

    2021-03-04 10:37:18.951342+0800 Test[87457:728941] ZKPerson - +load
    2021-03-04 10:37:18.951808+0800 Test[87457:728941] ZKPerson (Test)- +load
    2021-03-04 10:37:18.951891+0800 Test[87457:728941] ZKPerson (Test1)- +load
    2021-03-04 10:37:18.952038+0800 Test[87457:728941] ZKPerson(Test1) - +test
    Program ended with exit code: 0
    

    这里只打印了Test1分类的方法,因为编译时顺序Test1分类比Test分类后编译。但是注意,每个类的+load方法都执行了一次这是为什么?我们查看runtime源码:

    void call_load_methods(void)
    {
        static bool loading = NO;
        bool more_categories;
    
        loadMethodLock.assertLocked();
    
        // Re-entrant calls do nothing; the outermost call will finish the job.
        if (loading) return;
        loading = YES;
    
        void *pool = objc_autoreleasePoolPush();
    
        do {
            // 1. Repeatedly call class +loads until there aren't any more
    //首先会调用类的+load方法
            while (loadable_classes_used > 0) {
                call_class_loads();
            }
    
            // 2. Call category +loads ONCE
    //然后再调用分类的+load方法
            more_categories = call_category_loads();
    
            // 3. Run more +loads if there are classes OR more untried categories
        } while (loadable_classes_used > 0  ||  more_categories);
    
        objc_autoreleasePoolPop(pool);
    
        loading = NO;
    }
    

    由源码可知,每个类的+load方法都将调用一次。
    我们点进去call_class_loads()方法

    static void call_class_loads(void)
    {
        int i;
        
        // Detach current loadable list.
    //数组里面存放所有类
        struct loadable_class *classes = loadable_classes;
        int used = loadable_classes_used;
        loadable_classes = nil;
        loadable_classes_allocated = 0;
        loadable_classes_used = 0;
        
        // Call all +loads for the detached list.
        for (i = 0; i < used; i++) {
            Class cls = classes[i].cls;
    //拿到类里面的方法
            load_method_t load_method = (load_method_t)classes[i].method;
            if (!cls) continue; 
    
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
            }
    //通过指针直接调用
            (*load_method)(cls, SEL_load);
        }
        
        // Destroy the detached list.
        if (classes) free(classes);
    }
    

    我们看到+load方法的调用跟普通的方法调用不一样,它是直接从类里面拿到这个方法的地址,然后直接调用。所以每个类的+load方法都会调用。

    +load方法的调用顺序

    由上面源码可知,很显然+load方法的调用顺序是先调用原有类的+load方法,然后再调用分类的+load方法。那么原有类的+load方法调用顺序呢?如果有多个分类其调用顺序又是怎样呢?
    先看原有类的+load方法调用顺序

    void prepare_load_methods(const headerType *mhdr)
    {
        size_t count, i;
    
        runtimeLock.assertWriting();
    
        classref_t *classlist = 
            _getObjc2NonlazyClassList(mhdr, &count);
        for (i = 0; i < count; i++) {
    //将类放入数组中
            schedule_class_load(remapClass(classlist[i]));
        }
    
        category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
        for (i = 0; i < count; i++) {
            category_t *cat = categorylist[i];
            Class cls = remapClass(cat->cls);
            if (!cls) continue;  // category for ignored weak-linked class
            realizeClass(cls);
            assert(cls->ISA()->isRealized());
            add_category_to_loadable_list(cat);
        }
    }
    
    static void schedule_class_load(Class cls)
    {
        if (!cls) return;
        assert(cls->isRealized());  // _read_images should realize
    
        if (cls->data()->flags & RW_LOADED) return;
    
        // Ensure superclass-first ordering
    //递归,将父类传进去,把父类加进数组中
        schedule_class_load(cls->superclass);
    
        add_class_to_loadable_list(cls);
        cls->setInfo(RW_LOADED); 
    }
    

    从上面schedule_class_load()方法中可以看到会优先调用父类的+load方法。如果没有继承关系,那么就会按照编译的顺序去调用,先编译,先调用。
    +load方法调用顺序总结:

    1、首先是调用类的+load方法,按类的编译顺序调用(先编译,先调用),优先编译的先调用,调用子类的+load方法之前优先调用父类的+load方法
    2、然后调用分类的+load方法,按类的编译顺序调用(先编译,先调用),优先编译的先调用

    category中有load方法,且是在runtime加载类,分类的时候被调用
    问题:load方法能继承吗?
    答案:一般我们是不会主动去调用+load方法的,会等系统调用,一般继承是在我们调用方法的时候才会有意义。(存在于子类和父类之间)。是可以继承的,子类没有,去找父类的load方法
    相当于给子类发一个消息机制,objc_msgSend([ZKStudent class] @selector(load)) isa->元类未找到,通过superclass找到父元类里面的类方法

    本文参考此篇华文 https://www.jianshu.com/p/b21d3d717d27

    相关文章

      网友评论

          本文标题:OC基础-category(2)

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