在XCode15.0 的版本使用中,发现了这样一个问题:
+ (void)load {
static dispatch_once_t dbnOnceToken;
dispatch_once(&dbnOnceToken, ^{
Class clz = [self class];
swizzleInstanceMethod(clz, @selector(custom_viewWillAppear:), @selector(lh_viewWillAppear:));
swizzleInstanceMethod(clz, @selector(custom_viewDidAppear:), @selector(lh_viewDidAppear:));
swizzleInstanceMethod(clz, @selector(custom_viewWillDisappear:), @selector(lh_viewWillDisappear:));
swizzleInstanceMethod(clz, @selector(presentViewController:animated:completion:), @selector(new_presentViewController:animated:completion:));
});
}
这个代码执行了, 但是在运行过程中,并没有完成方法的交换, 也就是说, 在执行custom_viewWillAppear:
方法的时候,执行的就是 custom_viewWillAppear :
, 并没有被替换成 lh_viewWillAppear:
。
然后在Target
的 Build Setting
中, 把Other Linker Flags
添加-ld64
以后,方法又可以被成功替换了。
好奇之下, 在这个方法中添加了打印信息:
NSLog(@"😧======方法交换 %@", NSStringFromClass(clz));
![](https://img.haomeiwen.com/i1953382/7a7fec8fa536a9a9.png)
结果执行的时候发现这里打印的是:
😧======方法交换 那算咯 LMCACameraMainViewController
怎么会这么奇怪?!!!LMCACameraMainViewController
这个又是哪来的?!!!
点开LMCACameraMainViewController
这个类去看,发现这个LMCACameraMainViewController
是 LMBaseViewController
的子类, 子类为什么能替换掉父类的方法。
再看LMCACameraMainViewController
这个类发现同样实现了 +load
方法,实现如下:
+ (void)load{
[super load];
...
}
这里调用了[super load]
, 我们知道load 方法的调用顺序本身就是先执行父类, 再执行子类,这里应该是不需要自己手动调用 [super load]
方法了。
于是注释掉这个[super load]
方法,再次执行, 发现控制台打印正常了。
😧======方法交换 那算咯 LMBaseViewController
这里猜测可能是跟在编译阶段编译器的处理逻辑有关系, 在调用[super load]
方法以后,这个方法交换被转移到子类LMCACameraMainViewController
中, 父类LMBaseViewController
的方法并不会发生交换了。
LMBaseViewController
的load 方法中传入的指针指向的是子类,所以其他继承自LMBaseViewController
的类都不会执行 load方法了
Objc中的 +load 方法
- +load 方法是系统自动调用的,无需手动调用,系统自动为每一个类调用+load方法(如果有),所以也无需手动调用[super load]方法。
- +load 方法按照[SuperClass load]->[Class load]->[ChildClass load]的顺序加载。
- +load 方法是在所有类被加入到runtime以后调用的。
- [ChildClass load]方法是按照Compile Sources的排列顺序加载的,但要遵循调用[ChildClass load]之前,必须先调用其[SuperClass load]方法。
- 在所有类的+load方法调用完以后再调用[Category load]方法,[Category load]的调用顺序完全按照Compile Sources排列顺序。
调用的顺序
- +load 方法按照[SuperClass load]->[Class load]->[ChildClass load]的顺序加载。
- [ChildClass load]方法是按照Compile Sources的排列顺序加载的,但要遵循调用[ChildClass load]之前,必须先调用其[SuperClass load]方法。
这种现象是非常符合我们的直觉的,我们来分析一下这种现象出现的原因。
这是由于 schedule_class_load 有如下的实现:
static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized());
if (cls->data()->flags & RW_LOADED) return;
schedule_class_load(cls->superclass);
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
从这里可以看到,在传入一个cls 的时候,总是递归找到父类,并且加入到 loadable_classes 数组,从而确保其调用顺序的正确性。
- 在所有类的+load方法调用完以后再调用[Category load]方法,[Category load]的调用顺序完全按照Compile Sources排列顺序。
这是因为类与分类中 load 方法的调用顺序主要在 call_load_methods 中实现:
do {
while (loadable_classes_used > 0) {
call_class_loads();
}
more_categories = call_category_loads();
} while (loadable_classes_used > 0 || more_categories);
上面的 do while 语句能够在一定程度上确保,类的 load 方法会先于分类调用。但是这里不能完全保证调用顺序的正确。
如果分类的镜像在类的镜像之前加载到运行时,上面的代码就没法保证顺序的正确了,所以,我们还需要在 call_category_loads 中判断类是否已经加载到内存中(调用 load 方法):
目前看来在Xcode15上面,编译时候load方法并不是严格按照这个顺序执行的,这里先调用了子类的load方法
这里有两个讲解load方法比较好的文章,可以看下
网友评论