- +load
- +initialize
+load
、+initialize
这两个方法看起来都是在类初始的时候调用的,其实是在调用时机和runtime底层逻辑上有很大的不同。
+load
我们知道在程序开始运行的时候,在main()函数之前程序会准备很多的工作。其中runtime的初始化_objc_init(),就是在之前调用的。
在前面文章我们看过_objc_init()底层的代码:
void _objc_init (void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
environ_init(); //读取影响运行时的环境变量,如果需要,还可以打印环境变量
tls_init(); //关于线程key的绑定 例如析构函数设置静态密钥key
//运行c++静态构造函数,libc在dyld调用静态构造函数之前调用_objc_init(),
//所以我们必须自己做.
static_init();
runtime_init();
exception_init();
cache_init();
_imp_implementationWithBlock_init();
//注册镜像加载通知回调
/**
注:仅供objc运行时使用
映射、未映射和初始化objc映像时要调用的寄存器处理程序
Dyld将使用包含objc-image-info部分的镜像数组调用“mapped”函数
那些是dylib的镜像会自动替换ref计数,因此objc不再需要对它们调用dlopen()来防止它们被卸载
在调用_dyld_objc_notify_register()期间,dyld将调用已经加载了objc镜像的“映射”函数
在以后的dlopen()调用期间,dyld还将调用“mapped”函数
当Dyld在该镜像中被称为初始化器时,Dyld将调用“init”函数
这是objc调用图像中的任何+load方法的时候
*/
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
我们只关注load_images
里的逻辑
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
rwlock_writer_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
在load_images方法其实就是干了一件事,调用Class的+load()方法。
runtime调用+load()方法分为两步走:
- Discover load methods
- Call +load methods
接着往下看:
在Discover load methods中,会调用prepare_load_methods 来处理+load方法:
void prepare_load_methods(const headerType *mhdr)
{
size_t count, I;
runtimeLock.assertLocked();
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
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());
add_category_to_loadable_list(cat);
}
}
prepare_load_methods
逻辑分为两个部分:
1.先调用schedule_class_load
来组织class
的+load
方法
2.在调用add_category_to_loadable_list
来组织category
的+load
方法
然后schedule_class_load
方法实现如下:
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);
}
这是一个递归调用,内部调用会先add_class_to_loadable_list。把superclass用add_class_to_loadable_list方法到loadable class list中:
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++;
}
从上面的方法可以看出,每一个定义了
+load
的类,都会被放到loadable_classes
中。
也可以看出,+load
方法并不存在子类重写父类之说。而且父类的+load
方法会先于子类加到loadable class list
列表当中去,在递归取出调用的时候也是会先调父类的方法,后调用子类的方法。
刚刚是类里面的load方法,下面接着看add_category_to_loadable_list方法:
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++;
}
在
add_category_to_loadable_list
方法中,会将所有定义了+load
方法的category
都放到loadable_categories
队列中。
在所有load
方法都放完之后,最后就开始进行挨个调用。
call_load_methods
将定义了+load
方法的class
和category
分别放到loadable_classes
和 loadable_categories
队列后,runtime
就会依次读取队列中的class
和category
,并为之调用+load
方法:
* Locking: loadMethodLock must be held by the caller
* All other locks must not be held.
**********************************************************************/
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// 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;
}
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, @selector(load));
}
// Destroy the detached list.
if (classes) free(classes);
}
从上面的
call_load_methods
方法可以看出:
1.super class
的+load
方法调用时机是先于sub class
的
2.class
的+load
方法调用时机是先于category
的
+load总结
objc4源码解读过程:objc-os.mm
_objc_init
load_images
prepare_load_methods
schedule_class_load
add_class_to_loadable_list
add_category_to_loadable_list
call_load_methods
call_class_loads
call_category_loads
(*load_method)(cls, SEL_load)
+load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用
1.首先,load方法是一定会在runtime中被调用的,只要类被添加到runtime中了,如果类实现了就一定会调用load方法
2.load方法不会覆盖。如果子类实现了load方法,那么会先调用父类的load方法,然后又去执行子类的load方法。同样的,如果分类实现了load方法,也会先执行主类的load方法,然后再去执行分类的load方法。而且执行顺序是 父类 -> 子类 ->分类。
3.共同父类的各子类之间的load方法的执行顺序,主要依据于文件的编译顺序。先编译的文件中的load方法先调用。
4.同一个类的各分类之间的load方法的执行顺序,也是依据主要依据于文件的编译顺序。先编译的文件中的load方法先调用。(这和各分类之间通过消息发送机制调用同一个方法的逻辑不通,不能混淆。因为在add_category_to_loadable_list 方法内,把category中的load方法添加到loadable_categories中是顺序插入的,所以在取出调用的时候先编译的分类的load方法排在前面。而在类对象的methods中的方法,分类的方法是头插在methods列表中的,所以先编译的先加入,后编译的后加入也就排在了methods列表的前面,在方法调用走消息发送机制的时候,就会在查找到之后被先调用。)
5.要注意在runtime初始化的时候去调用+load方法,在这个时候+load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用也就是这个时候没有遵循消息的发送机制。
load补充
一:为了安全起见,我们的load里的操作每次都保证只去做一次。比如hook方法的时候。
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"%s",__FUNCTION__);
Method method1 = class_getInstanceMethod([self class], NSSelectorFromString(@"dealloc"));
Method method2 = class_getInstanceMethod([self class], @selector(deallocSwizzle));
method_exchangeImplementations(method1, method2);
});
}
一:对于不同关系的类之间的load的执行顺序。我们来验证一下。
首先看下第一次的编译顺序:
FatherClass
是父类
SubClass1、SubClass2
是FatherClass
的两个子类
Sub1Cate、Sub1Cate2
是SubClass1
的两个分类
Sub2Cate
是SubClass2
的分类
FatherCate
是FatherClass
的分类
父类不管顺序如何,优先级都是最高的,FatherClass的父类肯定是先执行。
然后看两个平级的子类。SubClass2比SubClass1先进行编译,因此SubClass2的load先执行。最后分分类,不管是父类还是子类的分类,都只看编译顺序,和是哪个类的分类没关系,应该是哪个分类先编译,哪个分类的load方法先执行。同样如果没有继承关系的类之间,load的执行顺序也都是按照编译顺序执行的
。
看打印,和我们分析的打印结果一致。
我们调整下编译的位置
看下打印:
还是如此。
+initialize
+ (void)initialize
{
if (self == [<#ClassName#> class]) {
<#statements#>
}
}
objc4源码解读过程
objc-msg-arm64.s
objc_msgSend
objc-runtime-new.mm
class_getInstanceMethod
lookUpImpOrNil
lookUpImpOrForward
_class_initialize
callInitialize
objc_msgSend(cls, SEL_initialize)
1.
+initialize
方法会在类第一次接收到消息时调用,相当于系统告诉你需要用到这个类了,把这个类里的相关内容准备好,准备被用到.
2.当第一次子类接收消息的时候。
2-0.如果分类中有+initialize
的实现。会在本子类的方法列表中找打最靠前的+initialize
的实现,然后依据2-1、2-2的逻辑流程走。
2-1.如果父类中有+initialize
的实现。先调用父类的+initialize
,再去调用子类的+initialize
。
2-2如果父类中的+initialize
之前调用过了,而且子类中自己也有+initialize
的实现,则只会去把自己的+initialize
实现就好了。
2-3.如果子类中没有+initialize
的实现,则回去调用父类的+initialize
的实现,不管父类之前有没有调用过+initialize
的实现,其实这是符合objc_msgSend的消息机制的,子类的方法列表中查询不到,就去父类的列表中找(从上面的源码过程中也能知道此时的objc_msgSend(cls, SEL_initialize)
中的cls
是子类的类对象。)。
(只要父类和子类中+initialize
的方法都有实现,就都会被调用
(其实是沿着继承连去一直找寻父类的+initialize
方法实现,第一次都会被初始化)。先初始化父类,再初始化子类,每个类只会初始化1次,虽然父类里的+initialize会被调用到多次,但是从判断的逻辑看只做了一次本类的初始化操作。)
3.+initialize
是通过objc_msgSend
进行调用的,(遵循消息发送机制)所以有以下特点:
(3.1)如果子类的方法列表里没有+initialize,会到父类的方法列表里去找+initialize(所以父类的+initialize可能会被调用多次,不代表父类被初始化了多次,这只是消息发送的机制,初始化的还是自身这个类)
(3.2)如果分类实现了+initialize,在类的方法列表methods里顺序会排在类本身的+initialize之前,所以会调用分类中的
总结一下:
+initialize
方法每个类在第一次接收消息的时候都会被初始化调用一次。遵循runtime的消息发送机制流程,只不过是在本类第一次初始化的时候,沿着继承链顺带着把继承链上的父类也都+initialize
初始化了一次,每个类只会初始化1次,但+initialize
方法不一定只调用一次。