美文网首页
iOS的 +load 和 +initialize 比较

iOS的 +load 和 +initialize 比较

作者: 小千 | 来源:发表于2018-06-25 18:31 被阅读27次

该文为load和initialize方法的简单记录,部分总结自如下文章,感谢作者分享:


+load方法

load方法的特点

当 类 或者 类别 加入Runtime中时(就是被引用的时候)就会执行 load 方法。
1. 父类 先于 子类执行;
2. 子类未实现,不会 调用父类的 load 方法;
3. 同级别有多个类时(如 B、B1类都继承于A类),则其 load 方法执行顺序 依赖于 Compile Sources 中的顺序;
4. 类的 load 方法 先于 类别执行,无论类别是哪个类的;
5. 有多个类别,无论类别是哪个类的,其执行顺序都 依赖于 Compile Sources 中的顺序。

demo验证特点

有如下几个类,他们的关系是:
XQC 类 继承于 XQB 类;而 XQB 类继承于 XQA 类。

1、在每个类的 .m 文件中,添加如下代码,来观察执行顺序:

+(void)load
{
    NSLog(@"%s",__FUNCTION__);
}

输出结果如下, 符合特点 父类先于子类执行

2018-06-25 15:52:53.469527+0800 TestLoadFunc[11712:298527] +[XQA load]
2018-06-25 15:52:53.471045+0800 TestLoadFunc[11712:298527] +[XQB load]
2018-06-25 15:52:53.471453+0800 TestLoadFunc[11712:298527] +[XQC load]

2、屏蔽 XQB 类 中的 load方法,输出结果如下,符合特点 子类未实现,不会调用父类的 load 方法

2018-06-25 15:52:53.469527+0800 TestLoadFunc[11712:298527] +[XQA load]
2018-06-25 15:52:53.471453+0800 TestLoadFunc[11712:298527] +[XQC load]

3、还原 XQB 类 中的 load 方法,添加XQB1类,继承于XQA,类,并且为XQB1类添加 load 方法。
输出结果如下:

2018-06-25 16:05:42.086176+0800 TestLoadFunc[11873:311360] +[XQA load]
2018-06-25 16:05:42.086941+0800 TestLoadFunc[11873:311360] +[XQB1 load]
2018-06-25 16:05:42.087752+0800 TestLoadFunc[11873:311360] +[XQB load]
2018-06-25 16:05:42.088013+0800 TestLoadFunc[11873:311360] +[XQC load]

查看Compile Sources中的顺序为 XQB1 类在 XQB 类之前,所以符合特点 同级别有多个类时,则其load方法执行顺序依赖于 Compile Sources 中的顺序

屏幕快照 2018-06-25 下午4.08.10.png

4、分别为 XQA、XQB、XQC类添加对应的类别 XQA+ACategory、XQB+BCategory、XQB+BCategory,分别为类别添加 load 方法,来观察执行顺序:

2018-06-25 16:05:42.086176+0800 TestLoadFunc[11873:311360] +[XQA load]
2018-06-25 16:05:42.086941+0800 TestLoadFunc[11873:311360] +[XQB1 load]
2018-06-25 16:05:42.087752+0800 TestLoadFunc[11873:311360] +[XQB load]
2018-06-25 16:05:42.088013+0800 TestLoadFunc[11873:311360] +[XQC load]
2018-06-25 16:05:42.088123+0800 TestLoadFunc[11873:311360] +[XQC(CCategory) load]
2018-06-25 16:05:42.088309+0800 TestLoadFunc[11873:311360] +[XQA(ACategory) load]
2018-06-25 16:05:42.088408+0800 TestLoadFunc[11873:311360] +[XQB(BCategory) load]

而查看Compile Sources中的顺序为:

屏幕快照 2018-06-25 下午4.14.07.png

所以,符合特点
类的 load 方法 先于 类别执行,无论类别是哪个类的。

有多个类别,无论类别是哪个类的,其执行顺序都依赖于 Compile Sources 中的顺序。

load 本质

load 方法是在运行时被执行的(main函数之前),其调用栈如下(可断点调试):

_ load_images  

//加载类和类别的load方法
└── load_images_nolock 

    //执行所有load方法
    └── call_load_methods

而在 load_images_nolock 方法中,则调用了 prepare_load_methods,其执行了两个方法:

_ prepare_load_methods  

//先将需要执行 load 的 class 添加到一个全局列表里 (loadable_class)
└── schedule_class_load 

    //然后将需要执行 load 的 category 添加到另一个全局列表里(loadable_category)
    └── add_category_to_loadable_list 

而在 schedule_class_load 方法中,确保先将父类添加到列表中

static void schedule_class_load(class_t *cls)
{
    assert(isRealized(cls));  // _read_images should realize

    if (cls->data->flags & RW_LOADED) return;

    //确保先将父类添加到全局列表里 (loadable_class)
    class_t *supercls = getSuperclass(cls);
    if (supercls) schedule_class_load(supercls);

    //再将当前类添加到全局列表里 (loadable_class)
    add_class_to_loadable_list((Class)cls);
    changeInfo(cls, RW_LOADED, 0); 
}

然后,在执行 call_load_methods 方法时:

_ call_load_methods  

//先遍历 loadable_classes 列表中的类,执行 load  方法。
└── call_class_loads 

    //然后再遍历 loadable_category 列表中的分类 ,执行 load  方法。
    └── call_category_loads 

而且在 call_class_loads 中,执行load方法的代码为:

// Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        IMP load_method = classes[i].method;
        if (!cls) continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", _class_getName(cls));
        }
        (*load_method) ((id) cls, SEL_load);
    }

所以由上可知,load 方法的本质:直接执行函数指针

所以,load 方法不会执行发送消息objc_msgSend那一套流程,这也解释了,为何子类、分类的load 方法不会覆盖父类的load 方法。


+initialize 方法

initialize方法的特点

在类、或者其子类,接收到第一条消息之前被执行。

1. 类引入项目,但未使用,则不会执行;
2. 父类 先于 子类执行;(同 load方法特点)
3. 子类未实现,则 会调用 父类的initialize方法;
4. 类的分类实现了 initialize方法,则 会覆盖 类中的 initialize方法;
5. 类存在多个分类,且都实现了 initialize 方法,则依赖于 Compile Sources 中的顺序,执行顺序中 最后一个分类initialize 方法。

demo验证特点

有如下几个类,他们的关系是:
XQC 类 继承于 XQB 类;而 XQB 类继承于 XQA 类。

1、在每个类的 .m 文件中,添加如下代码,来观察执行顺序:

+ (void)initialize{
     NSLog(@"%s",__FUNCTION__);
}

运行后,无任何打印信息。
符合特点,引入项目,没有使用,不会执行

2、然后初始化一个 XQC 类的对象:[XQC new];
运行后,输出结果为:


2018-06-25 17:06:13.430962+0800 TestLoadFunc[12333:367505] +[XQA initialize]
2018-06-25 17:06:13.431099+0800 TestLoadFunc[12333:367505] +[XQB initialize]
2018-06-25 17:06:13.431350+0800 TestLoadFunc[12333:367505] +[XQC initialize]

符合特点:父类先于子类执行

3、屏蔽 XQC 类的initialize方法,并将 XQB 类的initialize方法改为:

+ (void)initialize{
    NSLog(@"%@ %s",self,__FUNCTION__);
}

运行发现打印为:


2018-06-25 17:17:27.425518+0800 TestLoadFunc[12486:377950] +[XQA initialize]
2018-06-25 17:17:31.276542+0800 TestLoadFunc[12486:377950] XQB +[XQB initialize]
2018-06-25 17:17:35.670737+0800 TestLoadFunc[12486:377950] XQC +[XQB initialize]

可以看出,打印了两次 +[XQB initialize],按照官方解释

The superclass implementation may be called multiple times if subclasses do not implement initialize—the runtime will call the inherited implementation—or if subclasses explicitly call [super initialize]. If you want to protect yourself from being run multiple times, you can structure your implementation along these lines:

+ (void)initialize {
  if (self == [ClassName self]) {
    // ... do the initialization ...
  }
}

分析原因:因为 XQC 类继承自 XQB 类,子类未实现,则会调用父类的initialize方法,而XQB 类初始化时,第一次收到消息,也会调用自身的 initialize 方法,所以打印结果第一次是XQB 类调用了 initialize 方法,然后才是子类 XQC 类调用了 initialize 方法。

4、将XQC 类的initialize方法还原,为 XQB 类添加对应的类别 XQB+BCategory,并为类别添加initialize方法,运行来观察执行顺序:

2018-06-25 17:30:02.710611+0800 TestLoadFunc[12650:390749] +[XQA initialize]
2018-06-25 17:30:02.710751+0800 TestLoadFunc[12650:390749] +[XQB(BCategory) initialize]
2018-06-25 17:30:02.710855+0800 TestLoadFunc[12650:390749] +[XQC initialize]

符合特点:类的分类实现了 initialize方法,则会覆盖类中的 initialize方法。

5、再为 XQB 类添加一个类别 XQB+BCategory1,并为类别添加initialize方法,运行来观察执行顺序:

2018-06-25 17:37:19.533536+0800 TestLoadFunc[12734:397777] +[XQA initialize]
2018-06-25 17:37:19.533884+0800 TestLoadFunc[12734:397777] +[XQB(BCategory1) initialize]
2018-06-25 17:37:19.534419+0800 TestLoadFunc[12734:397777] +[XQC initialize]

观察Compile Sources 中的顺序为:

屏幕快照 2018-06-25 下午5.37.46.png
XQB+BCategory1.m 排在 XQB+BCategory.m 之后,所以符合特点:
类存在多个分类,且都实现了 initialize 方法,则依赖于 Compile Sources 中的顺序,执行顺序中 最后一个分类initialize 方法

initialize 方法的本质

查看 _class_initialize 源码

__private_extern__ void _class_initialize(Class cls)
{
    Class supercls;
    BOOL reallyInitialize = NO;

    // Get the real class from the metaclass. The superclass chain 
    // hangs off the real class only.
    cls = _class_getNonMetaClass(cls);

    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    supercls = _class_getSuperclass(cls);
    if (supercls  &&  !_class_isInitialized(supercls)) {
        _class_initialize(supercls);
    }
    
    // Try to atomically set CLS_INITIALIZING.
    monitor_enter(&classInitLock);
    if (!_class_isInitialized(cls) && !_class_isInitializing(cls)) {
        _class_setInitializing(cls);
        reallyInitialize = YES;
    }
    monitor_exit(&classInitLock);
    
    if (reallyInitialize) {
        // We successfully set the CLS_INITIALIZING bit. Initialize the class.
        
        // Record that we're initializing this class so we can message it.
        _setThisThreadIsInitializingClass(cls);
        
        // Send the +initialize message.
        // Note that +initialize is sent to the superclass (again) if 
        // this class doesn't implement +initialize. 2157218
        if (PrintInitializing) {
            _objc_inform("INITIALIZE: calling +[%s initialize]",
                         _class_getName(cls));
        }

        ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);

        if (PrintInitializing) {
            _objc_inform("INITIALIZE: finished +[%s initialize]",
                         _class_getName(cls));
        }        
        
        // Done initializing. 
        ......
}

分析源码:
1、优先执行父类的 initialize 方法。
通过 _class_getSuperclass取出父类,递归调用父类的 initialize 方法。

2、initialize 方法最终是通过 objc_msgSend 来执行的。


演示Demo

相关文章

网友评论

      本文标题:iOS的 +load 和 +initialize 比较

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