咱们常常会有一些需求:让某方法的执行先于main方法。好比要hook某个系统类的方法(hook UIViewController
的viewWillAppear
和viewDidDisappear
经过AOP的方式实现埋点功能),或者作一些初始化操做。这时候咱们常常首先想到的就是重写+(void) load {}
方法。c++
为何要放在load
方法中?咱们将从objc源码去探究。sql
main.m中的main
方法被称做程序的入口。但其实在执行到main以前系统首先要装载应用程序并完成动态连接,再初始化运行环境,而后准备好调用参数,调用main方法来启动应用程序并把执行权限交于咱们的代码。bash
咱们的代码会引用到UIKit
、Foundation
等框架,还有一些没有明确import的类库,好比libSystem
、libobjc
等。能够经过otool -L 可执行文件名称
来查看连接的动态库,他们都是dylib格式。app
otool -L XXApp.app/XXApp
复制代码
XXApp.app/XXApp:
/usr/lib/libbz2.1.0.dylib (compatibility version 1.0.0, current version 1.0.5)
/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 400.9.4)
/usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)
/usr/lib/libicucore.A.dylib (compatibility version 1.0.0, current version 62.1.0)
/usr/lib/libprotobuf.dylib (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libresolv.9.dylib (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libsqlite3.dylib (compatibility version 9.0.0, current version 274.22.0)
/usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.11)
/System/Library/Frameworks/ARKit.framework/ARKit (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/AVFoundation.framework/AVFoundation (compatibility version 1.0.0, current version 2.0.0)
复制代码
dyld是动态连接器(dynamic link editor),是苹果的动态链接器。系统先读取App的可执行文件(Mach-O),从里面得到dyld的路径,而后加载dyld,而后加载程序相关依赖库,对库进行连接,最后调用每一个依赖库的初始化方法。ojbc的初始化就发生在这个时机。框架
objc的初始化主要在_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();
static_init();
lock_init();
exception_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
复制代码
这里咱们能够看到,objc向dyld
注册了三个callback函数。当咱们的可执行文件的image load 后会执行load_images
方法。ui
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
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
复制代码
load_images方法中主要作了两件事情prepare_load_methods
,而后call_load_methods
。其实就是准备好load方法,而后调用(call)load方法。spa
咱们接着查看call_load_methods
方法的实现,在do { } while
中,首先调用class_loads
,而后再调用category_loads
。code
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; } 复制代码
load_images
的执行发生在main方法以前,因此可确保时机的要求。sqlite
而且咱们看到,class中的load
方法先执行,而后才会再执行category中重写的```load``方法。