之前有了解过+load 和 +initialize两个方法,但是一直以来没有总结,本篇文章将从以下几个方面着手:
-
方法调用时机
-
父类,子类,分类中的调用顺序
load方法
load方法在类被加入到objective-C runtime 中时调用,它在main方法调用之前,而且只会被调用一次。子类的load方法会在父类之后调用,category里的load方法会在本类之后调用。
在runtime的源代码中找到文件objc-runtime-new.mm,然后找到 void prepare_load_methods(header_info *hi) 函数
void prepare_load_methods(header_info *hi) { size_t count, i; rwlock_assert_writing(&runtimeLock); classref_t *classlist = _getObjc2NonlazyClassList(hi, &count); for (i = 0; i < count; i++) { schedule_class_load(remapClass(classlist[i])); } category_t **categorylist = _getObjc2NonlazyCategoryList(hi, &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 realizeClass(cls); assert(cls->ISA()->isRealized()); add_category_to_loadable_list(cat); } }
虽然不能完全理解每行代码的含义,但是我们大概能看懂, 这个函数是在提前准备满足+load方法调用条件的类和分类,以供接下来调用,其中在处理类时,调用了另外一个函数 static void schedule_class_load(Class cls) 它的实现时这样的
static void schedule_class_load(Class cls) { if (!cls) return; assert(cls->isRealized()); // _read_images should realize if (cls->data()->flags & RW_LOADED) return; // Ensure superclass-first ordering schedule_class_load(cls->superclass); add_class_to_loadable_list(cls); cls->setInfo(RW_LOADED); }
schedule_class_load(cls->superclass);
这个函数对父类进行递归操作,以确保父类优先执行。 准备好类和分类后,接下来就是对他们的+load方法进行调用了。打开objc-loadmethod.m 找到其中的void call_load_methods(void) 函数
void call_load_methods(void)
{
static BOOL loading = NO;
BOOL more_categories;
recursive_mutex_assert_locked(&loadMethodLock);
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
//1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
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;
}
很明显,大概的逻辑就是,调用上一步准备好的类和分类中的+load方法,并且先调用类后调用分类。我们查看这个函数中调用的一个关键函数 static void call_class_loads(void)
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_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, SEL_load);
}
// Destroy the detached list.
if (classes) _free_internal(classes);
}
其中 (*load_method)(cls, SEL_load),直接使用函数内存地址的方式,而不是使用消息发送的objc_msgsend的方式。这样的调用方式就使得 +load 方法拥有了一个非常有趣的特性,那就是子类、父类和分类中的 +load 方法的实现是被区别对待的。(这句有误:子类没实现,父类也会调用)也就是说如果子类没有实现 +load 方法,那么当它被加载时 runtime 是不会去调用父类的 +load 方法的。同理,当一个类和它的分类都实现了 +load 方法时,两个方法都会被调用。因此,我们常常可以利用这个特性做一些“邪恶”的事情,比如说方法混淆(Method Swizzling)。
+ initialize
+initialize 方法在类和父类收到第一条消息前被调用,也就是以懒加载的方式被调用,这样的好处是节约系统资源,避免浪费。结合runtime的源码加深对+initialize的调用
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
...
rwlock_unlock_write(&runtimeLock);
}
if (initialize && !cls->isInitialized()) {
_class_initialize (_class_getNonMetaClass(cls, inst));
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
// The lock is held to make method-lookup + cache-fill atomic
// with respect to method addition. Otherwise, a category could
...
}
以上是消息转发流程,从代码中可以看到,当类没有初始化时,runtime会调用void _class_initialize(Class cls) 对该类进行初始化。
void _class_initialize(Class cls)
{
...
Class supercls;
BOOL reallyInitialize = NO;
// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
_class_initialize(supercls);
}
// Try to atomically set CLS_INITIALIZING.
monitor_enter(&classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) {
cls->setInitializing();
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]",
cls->nameForLogging());
}
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
if (PrintInitializing) {
_objc_inform("INITIALIZE: finished +[%s initialize]",
...
}
虽然不能完全理解,但是我们可以看到代码中对父类进行了递归操作,以确保父类优先于子类初始化。另外最关键对是,runtime使用了发送消息objc_msgsend的方法对+initialize方法进行调用,也就是说它走的是发送消息对流程。如果子类没有实现这个方法,父类对实现就会被调用,如果分类实现了该方法,那么就会对这个类对实现进行覆盖。这个和+load方法是不同的。 因此,如果一个子类没有实现+initialize方法,那么父类的实现就会被多次执行。我们可以用下面的代码实现。
+ (void)initialize {
if (self == [ClassName self]) {
// ... do the initialization ...
}
}
结合demo进行验证 定义父类Person,子类Man,Woman,Man的分类Man+nice并且加入如下打印;
@implementation Person
+(void)load{
NSLog(@"%s",__func__);
}
@end
@implementation Man
+(void)load{
NSLog(@"%s",__func__);
}
@end
@implementation Man (nice)
+(void)load{
NSLog(@"%s",__func__);
}
@end
打印结果如下

1.子类没有实现,会不会调用父类的initlize方法
@implementation Person
+ (void)initialize{
NSLog(@"%s",__func__);
}
@end
@implementation Man
@end
@implementation Woman
@end
@implementation AViewController
- (void)viewDidLoad {
[super viewDidLoad];
Man* m = [Man new];
Woman* w = [Woman new];
}
打印结果

2.分类实现,会覆盖本类吗?
@implementation Man (nice)
+ (void)initialize{
NSLog(@"%s",__func__);
}
@end
@implementation Man
+ (void)initialize{
NSLog(@"%s",__func__);
}
@end

@implementation Man (nice)
+ (void)initialize{
NSLog(@"%s",__func__);
}
@end
@implementation Man
+ (void)initialize{
NSLog(@"%s",__func__);
}
@end
@implementation Man (Bad)
+ (void)initialize{
NSLog(@"%s",__func__);
}
@end

总结
通过查阅runtim的源码,我们知道了+load和+initialize方法的实现细节。明白了他们的调用机制和各自特点。总结如下
项目 | +load | +initialize |
---|---|---|
调用时机 | 被添加到runtime时main函数前 | 懒加载调用类的某个方法时 |
调用次数 | 1 | 多次 |
调用顺序 | 父类->子类->分类 | 父类->子类 |
调用细节 | 通过函数内存地址调用 | objc_msgSend |
分类中的实现 | 类和分类都会执行 | 覆盖类中的方法 |
load方法 调用顺序 | 父类->子类->分类 通过函数内存地址调用 所以父类,子类,不会覆盖 initlize 调用顺序:父类子类 走的是消息转发机制,所以如果子类没实现,父类可能会多次调用。 分类会覆盖类中对方法。 当方法替换时多用load方法,也可以用在initlize中,但是要注意会分类实现了,本类就不再调用,而且只有一个分类可以调用。