OC底层原理(六):Load和Initialize
Load方法什么时候调用?
load方法会在runtime加载类、分类的时候调用,且仅加载一次。
我们创建一个命令行项目,然后创建一个ZJPerson类和它的两个分类,Study、Life
@interface ZJPerson : NSObject
@end
@implementation ZJPerson
+ (void)load {
NSLog(@"ZJPerson Load");
}
@end
@interface ZJPerson (Study)
@end
@implementation ZJPerson (Study)
+ (void)load {
NSLog(@"ZJPerson(Study) Load");
}
@end
@interface ZJPerson (Life)
@end
@implementation ZJPerson (Life)
+ (void)load {
NSLog(@"ZJPerson(Life) Load");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
main函数中什么也不写,然后运行
截屏2021-01-11 21.03.44.png
为什么Load方法没有像分类的实例方法、类方法一样进行覆盖呢?
我们打开runtime源码来看
打开objc-os.mm文件,找到_objc_init方法
void _objc_init(void)
{
……
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
……
}
这次我们点击进入load_images方法
void
load_images(const char *path __unused, const struct mach_header *mh)
{
……
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
继续点击进入call_load_methods方法
void call_load_methods(void)
{
……
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
//调用类的load方法
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;
}
继续点击进入call_category_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方法
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方法
(*load_method)(cls, @selector(load));
}
// Destroy the detached list.
if (classes) free(classes);
}
从源码中我们可以看到是先调用类的load方法再调用分类的load方法
load方法并不是通过消息发送机制来调用的,而是直接访问地址来调用
Load方法调用顺序
在call_class_loads方法中我们可以看到是按顺序依次从loadable_classes数组中取出类再调用load方法的,那么loadable_classes数组又是在哪里加的呢?
我们需要回到上一层方法
void
load_images(const char *path __unused, const struct mach_header *mh)
{
……
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
//将类添加到loadable_classes中
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
点击进入prepare_load_methods方法
void prepare_load_methods(const headerType *mhdr)
{
……
//按照编译顺序取出类的数组列表
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
//依次遍历数组列表,将类添加到loadable_classes中
schedule_class_load(remapClass(classlist[i]));
}
//按照编译顺序取出分类的数组列表
category_t * const *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
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
realizeClassWithoutSwift(cls, nil);
ASSERT(cls->ISA()->isRealized());
//依次遍历数组列表,将分类添加到loadable_categories中
add_category_to_loadable_list(cat);
}
}
我们继续点击进入schedule_class_load方法
static void schedule_class_load(Class cls)
{
……
// Ensure superclass-first ordering
//递归调用
schedule_class_load(cls->getSuperclass());
//添加类进数组
add_class_to_loadable_list(cls);
……
}
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
method = cls->getLoadMethod();
if (!method) return; // Don't bother if cls has no +load method
if (PrintLoading) {
_objc_inform("LOAD: class '%s' scheduled for +load",
cls->nameForLogging());
}
if (loadable_classes_used == loadable_classes_allocated) {
loadable_classes_allocated = loadable_classes_allocated*2 + 16;
loadable_classes = (struct loadable_class *)
realloc(loadable_classes,
loadable_classes_allocated *
sizeof(struct loadable_class));
}
//依次添加类进数组
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
void add_category_to_loadable_list(Category cat)
{
IMP method;
loadMethodLock.assertLocked();
method = _category_getLoadMethod(cat);
// Don't bother if cat has no +load method
if (!method) return;
if (PrintLoading) {
_objc_inform("LOAD: category '%s(%s)' scheduled for +load",
_category_getClassName(cat), _category_getName(cat));
}
if (loadable_categories_used == loadable_categories_allocated) {
loadable_categories_allocated = loadable_categories_allocated*2 + 16;
loadable_categories = (struct loadable_category *)
realloc(loadable_categories,
loadable_categories_allocated *
sizeof(struct loadable_category));
}
//依次添加分类进数组
loadable_categories[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
loadable_categories_used++;
}
从上面的源码我们可以看出,当一个类是子类时会优先添加其父类进数组,之后再添加子类进数组,而别的类则是按编译顺序添加进数组
调用顺序总结
- 优先调用类的load方法
- 按照编译顺序调用load,先编译先调用load
- 在调用子类的load之前会先调用父类的load
- 其次调用分类的load方法
- 按照编译顺序调用load,先编译先调用load
我们通过代码来验证下,新增两个类ZJCat、ZJDog和ZJPerson的子类ZJStudent
@interface ZJCat : NSObject
@end
@implementation ZJCat
+ (void)load {
NSLog(@"ZJCat Load");
}
@end
@interface ZJDog : NSObject
@end
@implementation ZJDog
+ (void)load {
NSLog(@"ZJDog Load");
}
@end
@interface ZJStudent : ZJPerson
@end
@implementation ZJStudent
+ (void)load {
NSLog(@"ZJStudent Load");
}
@end
我们故意把ZJStudent的编译顺序放在ZJPerson的前面
截屏2021-01-12 21.32.48.png
然后运行项目,注意比较ZJDog、ZJStudent、ZJCat的顺序以及ZJStudent和ZJPerson的顺序,按照上面的顺序逻辑来推导,其打印结果应该是
ZJDog -> ZJPerson -> ZJStudent -> ZJCat -> ZJPerson(Life) -> ZJPerson(Study)
截屏2021-01-12 21.36.21.png
可以看到确实是我们分析的那个顺序
我们再调整顺序重新运行
截屏2021-01-12 21.40.32.png
按照逻辑分析,其打印顺序应该为
ZJPerson -> ZJCat -> ZJDog -> ZJStudent -> ZJPerson(Life) -> ZJPerson(Study)
截屏2021-01-12 21.42.02.png
Initialize什么时候调用?
initialize会在类第一次收到消息的时候,且只会调用一次
更新ZJPerson、Study分类、Life分类、ZJCat、ZJDog、ZJStudent代码至如下
@interface ZJPerson : NSObject
@end
@implementation ZJPerson
+ (void)initialize {
NSLog(@"ZJPerson initalize");
}
@end
@interface ZJPerson (Study)
@end
@implementation ZJPerson (Study)
+ (void)initialize {
NSLog(@"ZJPerson (Study) initalize");
}
@end
@interface ZJPerson (Life)
@end
@implementation ZJPerson (Life)
+ (void)initialize {
NSLog(@"ZJPerson (Life) initalize");
}
@end
@interface ZJCat : NSObject
@end
@implementation ZJCat
+ (void)initialize {
NSLog(@"ZJCat initalize");
}
@end
@interface ZJDog : NSObject
@end
@implementation ZJDog
+ (void)initialize {
NSLog(@"ZJDog initalize");
}
@end
@interface ZJStudent : ZJPerson
@end
@implementation ZJStudent
+ (void)initialize {
NSLog(@"ZJStudent initalize");
}
@end
然后main函数中什么也不写,运行项目
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
截屏2021-01-14 21.13.59.png
什么也没有打印
我们在main函数中初始化ZJPerson类
int main(int argc, const char * argv[]) {
@autoreleasepool {
[ZJPerson alloc];
}
return 0;
}
在运行项目
截屏2021-01-14 21.20.57.png
调用的确实分类的initialize方法,所以我们可以肯定在类第一次收到消息的时候,会在类查找方法的时候通过objc_msgSend来调用initialize方法
我们打开源码看下,搜索class_getInstanceMethod方法
Method class_getInstanceMethod(Class cls, SEL sel)
{
……
lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
……
}
点击进入lookUpImpOrForward方法
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
……
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
……
}
继续点击进入realizeAndInitializeIfNeeded_locked方法
static Class
realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize)
{
……
if (slowpath(initialize && !cls->isInitialized())) {
//没有初始化
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
……
}
return cls;
}
继续点击进入initializeAndLeaveLocked方法
static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock)
{
return initializeAndMaybeRelock(cls, obj, lock, true);
}
static Class initializeAndMaybeRelock(Class cls, id inst,
mutex_t& lock, bool leaveLocked)
{
……
initializeNonMetaClass(nonmeta);
……
}
继续点击进入initializeNonMetaClass方法
void initializeNonMetaClass(Class cls)
{
……
supercls = cls->getSuperclass();
//递归调用,优先初始化父类
if (supercls && !supercls->isInitialized()) {
initializeNonMetaClass(supercls);
}
……
callInitialize(cls);
……
}
点击进入callInitialize方法
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
asm("");
}
通过源码我们可以看到
- 调用子类initialize之前会先调用父类的initialize
- initialize是通过objc_msgSend消息发送机制来调用的
- 如果父类实现的initialize方法,而子类没有实现,则initialize方法会调用多次
- 如果分类实现了initialize方法会覆盖原有类的initialize方法
面试题
-
load方法在什么时候调用?
load方法在runtime加载类、分类的时候调用 -
load和initialize的区别?
调用时机
load方法在runtime加载类、分类的时候调用
initialize方法在类第一次收到消息时调用调用顺序
load调用顺序
-
优先调用类的load方法
- 按照编译顺序调用load,先编译先调用load
- 在调用子类的load之前会先调用父类的load
-
其次调用分类的load方法
- 按照编译顺序调用load,先编译先调用load
initialize调用顺序
在调用子类的initialize之前会先调用父类的initialize调用方式
load方法是runtime加载类、分类时通过load函数地址直接调用
initialize方法是通过objc_msgSend消息发送机制来调用的