美文网首页iOS进阶
iOS底层-系统方法load与initialize

iOS底层-系统方法load与initialize

作者: Engandend | 来源:发表于2019-12-26 16:50 被阅读0次

    +(void)load 与 + (void)initialize 是系统的2个比较特殊的类方法。

    其实这篇内容应该放在<iOS底层-APP加载>里面的,因为在app加载的源码中,有涉及到类加载的流程,但是由于那一部分内容我还没整理完,就没有发布出来,,而且发现这个内容如果想写清楚,内容也不短,干脆单独拿出来写一篇。

    方法 +(void)load +(void)initialize
    执行时机 在程序运行后立即执行 在类的方法第一次被调时执行
    若自身未定义,是否沿用父类的方法?
    类别中的定义 全都执行,但后于类中的方法 覆盖类中的方法, 只执行一个
    执行次数(非主动调用的情况下) 必然一次 0、1、多 次(调用者会不同)

    先看官方解释

    + initialize
    Initializes the class before it receives its first message.
    + load
    Invoked whenever a class or category is added to the Objective-C runtime; implement this method to perform class-specific behavior upon loading.

    + (void)load

    • load 方法在什么时候调用?
      官方解释是:运行时,添加类或者分类的时候调用。实现此方法以在加载时执行特定于类的行为。

    换句话说,load是在app启动的时候加载,从源码中找

    _objc_init —>
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

    这里面的2个方法 map_imagesload_images, map_images的作用就是加载所有的类/协议/分类,加载完成之后,就开始调用load_images,在这个方法里面看:

    load_images(const char *path __unused, const struct mach_header *mh)
    {
        ......
        {
            mutex_locker_t lock2(runtimeLock);
            prepare_load_methods((const headerType *)mh);    // 把所有需要load的类 加载一个list里面
        }
        call_load_methods();    // 调用load方法
    }
    
    
    void call_load_methods(void)
    {
       ......
        do {
            while (loadable_classes_used > 0) {
                call_class_loads();                     // 先加载类的load 
            }
            more_categories = call_category_loads();    // 在加载category的load
    
        } while (loadable_classes_used > 0  ||  more_categories);   
       ......
    }
    
    

    call_load_methods 的官方解释:
    ---->
    Call all pending class and category +load methods. //先加载类的load方法
    Class +load methods are called superclass-first. //类的load方法 父类的优先
    Category +load methods are not called until after the parent class's +load. //在调用分类的load
    ....
    官方解释已经很清楚了,先加载类的load,如果其有父类,先加载父类,在加载子类,最后加载分类。

    但是这里依然有一个疑问,官方解释没有说清楚,1、分类的加载顺序是怎样的?2、不同的类之间的加载顺序是什么样的?
    其实在源码中有可以看到:

    void prepare_load_methods(const headerType *mhdr)
    {
    ......
        classref_t *classlist = 
            _getObjc2NonlazyClassList(mhdr, &count);    // 1、按照编译顺序加载所有的类(不包括分类)
        for (i = 0; i < count; i++) {
            schedule_class_load(remapClass(classlist[i]));  // 2、在这里  按照先父类 在子类的方式加入列表
        }
    
        category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);  // 分类也是类似的方式
        for (i = 0; i < count; i++) {
           ......
            add_category_to_loadable_list(cat);
        }
    }
    
    
    static void schedule_class_load(Class cls)
    {
      .......
        schedule_class_load(cls->superclass);    //递归加载,先加载父类
    
        add_class_to_loadable_list(cls);
    }
    
    

    由以上代码中 1、2备注,得出 :
    1、先加载类的load:类的加载是按照编译顺序,同时遵循先父类再子类的方式
    2、再加载分类的load:分类直接按照编译顺序,和其绑定类的继承没有关系

    • 用代码打印的方法来验证以上结论
      新建类:
      Person:NSObject---------Student:Person
      Student+JE1 ----------Student+JE2
      Person+JE -----------OtherClass:NSObject
      每个类中重写+(void)load方法
    + (void)load
    {
        NSLog(@"%s",__func__);
    }
    

    在main.m中 int int main(int argc, char * argv[]) { 这行打断点并执行

      • 顺序一


        编译顺序.png

    看打印顺序

    课件学习[48254:1941721] +[Person load]
    课件学习[48254:1941721] +[Student load]
    课件学习[48254:1941721] +[OtherClass load]
    课件学习[48254:1941721] +[Student(JE2) load]
    课件学习[48254:1941721] +[Person(JE) load]
    课件学习[48254:1941721] +[Student(JE1) load]
    
      • 顺序二: 换一下编译顺序, 将Person.m换到最下面,分类随便换个位置
        在看打印顺序


        编译顺序.png
    课件学习[48337:1943749] +[Person load]
    课件学习[48337:1943749] +[Student load]
    课件学习[48337:1943749] +[OtherClass load]
    课件学习[48337:1943749] +[Student(JE2) load]
    课件学习[48337:1943749] +[Student(JE1) load]
    课件学习[48337:1943749] +[Person(JE) load]
    
      • 顺序三 : 把OtherClass.m从编译列表里面删除(注意:不是从工程中删除源文件)


        编译顺序.png
    课件学习[48555:1948781] +[Person load]
    课件学习[48555:1948781] +[Student load]
    课件学习[48555:1948781] +[Person(JE) load]
    课件学习[48555:1948781] +[Student(JE2) load]
    课件学习[48555:1948781] +[Student(JE1) load]
    

    结论

    由此得出结论:
    1、先加载类的load
    2、在加载分类的load
    3、不同的类之间加载顺序为:有继承关系的,先加载父类load、再加载子类的load,无继承关系的,按照编译顺序
    ----比如顺序二 Student、OtherClass、Person,先加载Student的load,由于Person是Student的父类,所以Person的顺序比OtherClass早
    4、分类的加载顺序是完全按照编译顺序,也就是谁在前面,谁先加载。和其绑定类的继承关系无关
    ----比如顺序二中,Student继承Person,但是其分类的顺序是 Student+JE2、Student+JE1、Person+JE,顺序是什么样,加载load就是什么样。
    5、即使有类的源文件,但是编译列表中没有,那么这个类就不会被编译,也就不会执行其load方法

    + (void)initialize

    • initialize方法在什么时候调用?
      官方解释是:在类收到第一条消息之前初始化它。

    换句话说,就是第一次用到它之前调用,比如初始化一个对象(其父类也会调用initialize)、调用类方法等。 从源码中找:

    class_initialize -> initializeNonMetaClass()

    void initializeNonMetaClass(Class cls)
    {
        .......
        // Make sure super is done initializing BEFORE beginning to initialize cls.
        // See note about deadlock above.
        supercls = cls->superclass;
        if (supercls  &&  !supercls->isInitialized()) {
            initializeNonMetaClass(supercls);        // 递归加载父类的initialize
        }
        ........
    }
    
    

    用代码实际打印来看看结果:
    新建类:
    Person:NSObject---------Student:Person
    Student+JE1 ----------Student+JE2
    Person+JE -----------OtherClass:NSObject

      • 验证一:每个主类中重写+(void)initialize方法 (注意:分类先不写
    + (void)initialize
    {
        NSLog(@"class:%@ %s",NSStringFromClass(self),__func__);
    }
    

    在main.m 中调用[Student new]; 查看打印结果:

    课件学习[56973:2105697] class:Person +[Person initialize]
    课件学习[56973:2105697] class:Student +[Student initialize]
    

    只打印了Person和Student的initialize方法,OtherClass的没有打印:

    说明:没有发送消息的类不会调用initialize

      • 验证二:每个主类中重写+(void)initialize方法 ,分类也重写
        在main.m 中调用[Student new]; 查看打印结果:
    课件学习[57164:2109650] class:Person +[Person(JE) initialize]
    课件学习[57164:2109650] class:Student +[Student(JE1) initialize]
    

    只打印了分类中Person(JE)和Student(JE1)这2个分类的initialize方法

    说明:如果主类有相应的分类(或多个分类),会调用分类中的initialize方法,具体调用的是哪个分类的方法,由编译顺序决定。

      • 验证三:Student相关的类中的initialize不写(包括Student和相对应的2个分类),其他的不变。
        在main.m 中调用[Student new]; 查看打印结果:
    课件学习[57441:2114965] class:Person +[Person(JE) initialize]
    课件学习[57441:2114965] class:Student +[Person(JE) initialize]
    

    打印了2次Person(JE)里面的initialize方法,但是调用者不同

    说明:当子类没有重写initialize方法,这个时候回去执行父类的initialize方法

    总结

    1、initialize的执行顺序为先父类、在子类
    2、分类的initialize方法会覆盖主类的方法(假覆盖,方法都在,只是没有执行)
    3、只有在这个类有发送消息的时候才会执行initialize,比如初始化对象、调用类方法等。
    4、多个分类的情况,只执行一次,具体执行哪个分类的initialize,有编译顺序决定(Build Phases -> Compile Sources 中的顺序)
    5、如果子类没有重写initialize,那么会调用其父类的initialize方法

    相关文章

      网友评论

        本文标题:iOS底层-系统方法load与initialize

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