美文网首页
load方法底层实现

load方法底层实现

作者: whbsspu | 来源:发表于2018-07-27 09:57 被阅读27次

    load方法

    load方法什么时候调用?

    1、load方法是在runtime加载类和分类的时候调用。
    论证:创建一个Person类和它的两个分类,然后分别重写各自的load方法:

    //Person
    + (void)load{
        
        NSLog(@"Person + load");
    }
    
    //Person+Test1
    + (void)load{
        
        NSLog(@"Person (Test1) + load");
    }
    
    //Person+Test2
    + (void)load{
        
        NSLog(@"Person (Test2) + load");
    }
    

    然后我们什么也不做,直接运行代码,打印结果如下:

    2018-07-24 20:45:08.369170+0800 interview - Category[14157:409819] Person + load
    2018-07-24 20:45:08.371806+0800 interview - Category[14157:409819] Person (Test1) + load
    2018-07-24 20:45:08.373190+0800 interview - Category[14157:409819] Person (Test2) + load
    

    通过打印结果我们可以看到,Person类及其分类的load方法都被调用了,但是我们什么也没做,直接运行的代码,这也就证实了load方法是由runtime加载类和分类的时候调用的。

    我们再给Person及其子类添加一个+(void)test方法并实现它,如下所示:

    //Person
    + (void)test{
        
        NSLog(@"Person + test");
    }
    
    //Person+Test1
    + (void)test{
        
        NSLog(@"Person (Test1) + test");
    }
    
    //Person+Test2
    + (void)test{
        
        NSLog(@"Person (Test2) + test");
    }
    

    然后在调用Person的test方法:

    [Person test];
    

    得到的打印结果如下:

    2018-07-24 21:07:32.886316+0800 interview - Category[14670:428685] Person + load
    2018-07-24 21:07:32.887195+0800 interview - Category[14670:428685] Person (Test1) + load
    2018-07-24 21:07:32.887461+0800 interview - Category[14670:428685] Person (Test2) + load
    2018-07-24 21:07:33.050735+0800 interview - Category[14670:428685] Person (Test2) + test
    

    通过打印结果我们可以看到,Person (Test2)的test方法被调用了,如果分类和类同时实现了一个方法,那么分类中的方法和类中的方法都会保存到内存中,并且分类的方法在前,类的方法在后,这样在调用的时候就会优先找到分类的方法,给人的感觉就是好像类的方法被覆盖了一样。

    那么问题来了,同样是类方法,同样是分类中实现了类的方法,为什么load方法不像test方法一样,调用分类的实现,而是类和每个分类中的load方法都被调用了呢?load方法到底有什么不同呢?
    要想弄清楚其中的原理,我们还是要从runtime的源码入手:

    1、找到objc-os.mm这个文件,然后找到这个文件的void _objc_init(void)这个方法,runtime的初始化都是在这个方法里面完成。
    2、这个方法的最后一行调用了函数_dyld_objc_notify_register(&map_images, load_images, unmap_image);,我们点进load_images,这是加载模块的意思。

    1.png 3、 2.png 4、 3.png 5、我们点进call_class_loads();这个方法查看对类的load方法的调用过程: 4.png 6、然后我们再点进call_category_loads()查看对分类的load方法的调用过程: 5.png

    那么这样我们就搞清楚了为什么load方法不是像test方法一样,执行分类的实现:

    因为load方法的调用并不是objc_msgSend机制,它是直接找到类的load方法的地址,然后调用类的load方法,然后再找到分类的load方法的地址,再去调用它。

    而test方法是通过消息机制去调用的。首先找到类对象,由于test方法是类方法,存储在元类对象中,所以通过类对象的isa指针找到元类对象,然后在元类对象中寻找test方法,由于分类也实现了test方法,所以分类的test方法是在类的test方法的前面,首先找到了分类的test方法,然后去调用它。

    有继承关系时load方法的调用顺序

    通过上面的分析我们确定了load方法的一个调用规则:先调用所有类的load方法,然后再调用所有分类的load方法。
    //Student
    + (void)load{
        
        NSLog(@"Student + load");
    }
    
    //Student (Test1)
    + (void)load{
        
        NSLog(@"Student (Test1) + load");
    }
    
    //Student (Test2)
    + (void)load{
        
        NSLog(@"Student (Test2) + load");
    }
    

    然后我们运行一下程序,看打印结果:

    2018-07-25 15:45:58.605156+0800 interview - Category[13869:359239] Person + load
    2018-07-25 15:45:58.605684+0800 interview - Category[13869:359239] Student + load
    2018-07-25 15:45:58.606420+0800 interview - Category[13869:359239] Student (Test2) + load
    2018-07-25 15:45:58.606870+0800 interview - Category[13869:359239] Person (Test1) + load
    2018-07-25 15:45:58.607293+0800 interview - Category[13869:359239] Student (Test1) + load
    2018-07-25 15:45:58.607514+0800 interview - Category[13869:359239] Person (Test2) + load
    2018-07-25 15:45:58.812025+0800 interview - Category[13869:359239] Person (Test2) + test
    

    通过打印结果我们可以很清楚的看见,Person类和Student类的load方法先被调用,然后调用分类的load方法。再运行多次,都是Person类和Student类的load方法先被调用,然后分类的方法才被调用。并且总是Person类的load在Student类的load方法前面被调用,这会不会和编译顺序有关呢?我们改变一下编译顺序看看:

    TARGETS -> Build Phases -> Complle Sources中文件的放置顺序就是文件的编译顺序。

    11.png

    目前是Person类在Student类的前面编译,现在我们把Student类放到Person类的前面编译:


    12.png

    然后我们再运行一下程序,查看打印结果:

    2018-07-25 15:56:07.270034+0800 interview - Category[14070:367686] Person + load
    2018-07-25 15:56:07.270619+0800 interview - Category[14070:367686] Student + load
    2018-07-25 15:56:07.271107+0800 interview - Category[14070:367686] Student (Test2) + load
    2018-07-25 15:56:07.271494+0800 interview - Category[14070:367686] Person (Test1) + load
    2018-07-25 15:56:07.271762+0800 interview - Category[14070:367686] Student (Test1) + load
    2018-07-25 15:56:07.272118+0800 interview - Category[14070:367686] Person (Test2) + load
    2018-07-25 15:56:07.433068+0800 interview - Category[14070:367686] Person (Test2) + test
    

    我们发现还是Person类的load方法在Student类前面被调用,所以好像和编译顺序无关呀。那么我们就需要思考一下是不是由于Student和Person之间的继承关系导致的呢?

    在runtime源码中我们找到了这样一段源码:

    21.png

    通过这个方法我们就可以很清晰的看到,当要把一个类加入最终的这个classes数组的时候,会先去上溯这个类的父类,先把父类加入这个数组。
    由于在classes数组中父类永远在子类的前面,所以在加载类的load方法时一定是先加载父类的load方法,再加载子类的load方法。

    类的load方法调用顺序搞清楚了我们再来看一下分类的load方法调用顺序

    还是在runtime源码里找到下面一段代码:


    22.png

    通过这个分析我们就能知道,分类的load方法加载顺序很简单,就是谁先编译的,谁的load方法就被先加载。

    总结 load方法调用顺序

    1.先调用类的load方法

    按照编译先后顺序调用(先编译,先调用)
    调用子类的load方法之前会先调用父类的load方法

    2.再调用分类的load方法

    按照编译先后顺序,先编译,先调用

    相关文章

      网友评论

          本文标题:load方法底层实现

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