iOS面试题:+load与+initialize

先来看一个表

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

先看官方解释:

一、+load

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

+load方法是一定会在runtime中被调用的,只要类被添加到runtime中了,就会调用+load方法,即只要是在Compile Sources中出现的文件总是会被装载,与这个类是否被用到无关,因此+load方法总是在main函数之前调用。所以我们可以自己实现+laod方法来在这个时候执行一些行为。

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

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

这里面的2个方法 map_images 和 load_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);   
   ......
}

另外有意思的一点是,+load方法不会覆盖。也就是说,如果子类实现了+load方法,那么会先调用父类的+load方法,然后又去执行子类的+load方法。但要注意的时+load方法只会调用一次。而且执行顺序是:类 ->父类-> 子类 ->分类。而不同类之间的顺序不一定。

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

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:分类直接按照编译顺序,和其绑定类的继承没有关系

总结

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方法

二、+initialize

initialize方法在什么时候调用?
官方解释是:在类收到第一条消息之前初始化它。
换句话说,就是第一次用到它之前调用,比如初始化一个对象(其父类也会调用initialize)、调用类方法等。 从源码中找:

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

如果主类有相应的分类(或多个分类),会调用分类中的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
    }
    ........
}

+initialize方法是在类或它的子类收到第一条消息之前被调用的,这里所指的消息包括实例方法和类方法的调用,并且只会调用一次。+initialize方法实际上是一种惰性调用,也就是说如果一个类一直没被用到,那它的+initialize方法也不会被调用,这一点有利于节约资源。
+load方法不同,却更符合我们预期的就是,+initialize方法会覆盖。也就是说如果子类实现了+initialize方法,就不会执行父类的了,直接执行子类本身的。如果分类实现了+initialize方法,也不会再执行主类的。所以+initialize方法的执行覆盖顺序是:分类 -> 子类 ->类。且只会有一个+initialize方法被执行。

总结

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

三、两者的异同

1、相同点

1).load和initialize会被自动调用,不能手动调用它们。
2).子类实现了load和initialize的话,会隐式调用父类的load和initialize方法。
3).load和initialize方法内部使用了锁,因此它们是线程安全的。

2、不同点

1).调用顺序不同,以main函数为分界,+load方法在main函数之前执行,+initialize在main函数之后执行。
2).子类中没有实现+load方法的话,不会调用父类的+load方法;而子类如果没有实现+initialize方法的话,也会自动调用父类的+initialize方法。
3).+load方法是在类被装在进来的时候就会调用,+initialize在第一次给某个类发送消息时调用(比如实例化一个对象),并且只会调用一次,是懒加载模式,如果这个类一直没有使用,就不回调用到+initialize方法。

四、使用场景

1.+load一般是用来交换方法Method Swizzle,由于它是线程安全的,而且一定会调用且只会调用一次,通常在使用UrlRouter的时候注册类的时候也在+load方法中注册。
2.+initialize方法主要用来对一些不方便在编译期初始化的对象进行赋值,或者说对一些静态常量进行初始化操作。

你可能感兴趣的:(iOS面试题:+load与+initialize)