最近面试遇到这样的问题,一般来说我们会在+load
和+initialize
进行method swizzle
,那么两个方法差异在哪里
执行时机
load
+load
方法是在main
函数之前被调用,先调用类的+load
,再调用分类的+load
,且每个+load
方法只执行一次。
这里的只执行一次是值得是每个文件中执行一次,因为类要调用+load
,分类也要调用+load
+load
方法由dyld触发,整个流程如下
_objc_init
主要是_dyld_objc_notify_register(&map_images, load_images, unmap_image);
注册3个方法map_images
读取image
_read_images
依次读取mach-o
各个section
reMethodizeClass
生成class_rw_t
load_images
调用call_load_methods
方法call_load_methods
调用call_class_loads
call_class_loads
中直接调用存储的IMP
,而非方法调用
子类调用+load
时,会先调用父类的+load
,但是仅仅都是只会执行一次。
+initialize
和+load
的区别在于,+load
只能执行一次,而+initialize
会执行多次,为什么呢,因为我们知道+initialize
是消息机制触发的,当一个子类中没有+initialize
方法时,它自然会执行父类的+initialize
方法,而+load
方法逻辑是,当该类没有实现+load
时,则会跳过!
看下+load
的实现:
/***********************************************************************
* add_class_to_loadable_list
* Class cls has just become connected. Schedule it for +load if
* it implements a +load method.
**********************************************************************/
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++;
}
再看下+initialize
实现,是通过objc_msgSend
触发的:
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
initialize
initialize
的调用时机则比较延迟,是在对该类发送的第一个消息时,即对该类调用objc_msgSend
方法时,就会调用。
例如如下代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
[LGPerson className];
LGPerson *objc1 = [[LGPerson alloc] init];
id __weak objc2 = objc1;
id __weak objc3 = objc2;
// 对象相等 = hash
}
return 0;
}
通过debug调试,[LGPerson className]
执行时就会触发+initailize
方法,而不用等到alloc
执行
其堆栈如下:
对于+initialize
是线程安全的,当一个线程调用+initialize
时,另一个线程调用将会被阻塞,至到+initialize
完成,由于阻塞原因,所以其不应该执行太复杂的逻辑,见 官方文档
此外子类如果未实现+initialize
则会调用[super initialize].
,如果要防止多次调用,可以如下做法:
+ (void)initialize {
if (self == [ClassName self]) {
// ... do the initialization ...
}
}
self
指的是当前的receiver
,而[ClassName self]
必定是ClassName
,
对于self
和super
,前者是先从本身类中寻找方法,而后者是从父类中寻找方法。只是寻找方法的起点不一,真实的接收对象receiver还是self,这也是[self class]
和[super class]
在init
方法中输出一致的原因。
+initialize
则是在初次使用这个类时调用,那么就有这样的问题。
如下:
Person
继承NSObject
Teacher
继承Person
当使用[Person new]
时无疑会调用Person
的+initialize
,然后,当再使用[Teacher new]
时无疑会调用Teacher
的+initialize
吗,答案是不会的。
我们加上日志:
#import "Person.h"
@implementation Person
+ (void)initialize {
NSLog(@"%@ initialize",[self class]);
}
@end
#import "Teacher.h"
@implementation Teacher
+ (void)initialize {
NSLog(@"%s initialize",__FUNCTION__);
}
@end
case 1
Teacher *peo = [Person new];
NSLog(@"------------------");
Teacher *mircle = [Teacher new];
这段代码的输出如下:
+[Person initialize] initialize
------------------
+[Teacher initialize] initialize
case 2
NSLog(@"------------------");
Teacher *mircle = [Teacher new];
输出为:
------------------
+[Person initialize] initialize
+[Teacher initialize] initialize
当Teacher
中没有重写+initialize
时,会默认调用父类Person
的+initialize
实现。
method swizzle
我们使用+initialize
中进行方法交换,避免冷启动时main
函数之前的加载过程过长。
代码如下:
#import "Person.h"
#import <objc/runtime.h>
@implementation Person
+ (void)initialize {
NSLog(@"%s initialize",__FUNCTION__);
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originMethod = class_getInstanceMethod([self class], @selector(hello));
Method exMethod = class_getInstanceMethod([self class], @selector(hi));
if (class_addMethod([self class], @selector(hello), method_getImplementation(exMethod), method_getTypeEncoding(exMethod))) {
class_replaceMethod([self class], @selector(hi), method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
}else{
method_exchangeImplementations(originMethod, exMethod);
}
});
}
- (void)hello {
NSLog(@"%s hello",__FUNCTION__);
}
- (void)hi {
NSLog(@"%s hi",__FUNCTION__);
}
@end
#import "Teacher.h"
#import <objc/runtime.h>
@implementation Teacher
+ (void)initialize {
NSLog(@"%s initialize",__FUNCTION__);
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originMethod = class_getInstanceMethod([self class], @selector(hello));
Method exMethod = class_getInstanceMethod([self class], @selector(standup));
if (class_addMethod([self class], @selector(hello), method_getImplementation(exMethod), method_getTypeEncoding(exMethod))) {
class_replaceMethod([self class], @selector(standup), method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
}else{
method_exchangeImplementations(originMethod, exMethod);
}
});
}
//- (void)hello {
// NSLog(@"%s hello2",__FUNCTION__);
//}
- (void)standup {
NSLog(@"%s standup",__FUNCTION__);
}
@end
执行下列语句:
Person *peo = [Person new];
Teacher *mircle = [Teacher new];
[peo hello];
[mircle standup];
结果如下:
+[Person initialize] initialize
+[Teacher initialize] initialize
-[Person hi] hi
-[Person hi] hi
Teacher
本意是交换hello
和standup
,但是由于父类已经交换了hello
和hi
,所以最终为hi
和standup
交换。这也是方法交换不安全的地方。
另外一种情况,当Teacher
中有自己的实现hello
方法时
#import "Teacher.h"
#import <objc/runtime.h>
@implementation Teacher
+ (void)initialize {
NSLog(@"%s initialize",__FUNCTION__);
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originMethod = class_getInstanceMethod([self class], @selector(hello));
Method exMethod = class_getInstanceMethod([self class], @selector(standup));
if (class_addMethod([self class], @selector(hello), method_getImplementation(exMethod), method_getTypeEncoding(exMethod))) {
class_replaceMethod([self class], @selector(standup), method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
}else{
method_exchangeImplementations(originMethod, exMethod);
}
});
}
- (void)hello {
NSLog(@"%s hello2",__FUNCTION__);
}
- (void)standup {
NSLog(@"%s standup",__FUNCTION__);
}
@end
结果输出如下:
+[Person initialize] initialize
+[Teacher initialize] initialize
-[Person hi] hi
-[Teacher hello] hello2
确是交换正常了,因为直接从本类method list
中交换了实现,没有交换父类那个已经交换过的了。
人生何处不相逢,金樽对玉琼,望君一饮而过,豪情万丈