详解load 函数
因为下载了一个可以调试的runtime代码,所以重新阅读以下runtime的一些代码,写点栗子跑一跑
众所周知,load 的调用时机是这个class加载的时候就调用的,这篇文章主要弄清楚
- load调用的具体时机
- 父类子类的load调用 顺序
- 分类 和 原始类的 load 调用顺序
- 子类分类load 的调用顺序
暂时就这么多。ok,我们随便建立一个工程,
@implementation ViewController
+ (void)load {
NSLog(@"ViewController");
}
@end
@interface sonViewController : ViewController
@end
@implementation sonViewController
+ (void)load {
NSLog(@"sonViewController");
}
@end
@implementation ViewController (ll)
+ (void)load {
NSLog(@"ViewController (ll)");
}
@end
@implementation sonViewController (ll)
+ (void)load {
NSLog(@"sonViewController (ll)");
}
@end
我们看看打印结果:
ViewController
sonViewController
ViewController (ll)
sonViewController (ll)
我们可以清楚的看到,调用顺序是这样子的。我们可以通过其他手段来看看,为什么是这种调用顺序。从git 上下载一份可以调试的runtime源码。
https://github.com/0xxd0/objc4
运行,没有问题的话我们就可以巴拉巴拉这个代码了。
重点在这个函数
从函数命名可以看出,是调用load 函数的。
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;
}
从这个函数可以看出, 在一个 do {}while 循环里面,做的事情,
不断的尝试调用 call_class_loads ,全局有一个列表,来标示,稍后分析。
然后才调用分类的load函数。
more_categories = call_category_loads();
调用分类的load 函数。。
此处,我们能得出结论,先调用所有已经加载类的load函数,然后调用分类的load.那么子类和父类的调用关系在哪里看呢?
我们查看调用类load 方法的函数
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方法
(*load_method)(cls, SEL_load);
}
// Destroy the detached list.
if (classes) free(classes);
}
从这里,我们可以看到,就是全局有一个loadable_classes 数组,然后顺序遍历这个数组,调用对应的load方法。所以,父类子类的关系,肯定是父类子类放入表中的时机不一样。我们重点关注一下,放入先后顺序问题上。
调用顺序如下:
{
load_images
prepare_load_methods
schedule_class_load
add_category_to_loadable_list
}
通过 prepare_load_methods 函数,可以看出,先把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 之前,先尝试 superView schedule_class_load(cls->superclass);
如果superView 已经加载过了就return;
这就是为什么super的load 比子类的load调用时机早
分类的load方法调用顺序,完全取决于 _getObjc2NonlazyCategoryList 获取到的分类顺序啦!
我们来理一理重要函数的代码模块
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);
}
runtime 一开始会执行这个函数,这个函数做了两件事情,初始化工作,和 注册事件,通过 _dyld_objc_notify_register 注册了 load_images 函数,如果有新的镜像被加入,load_images 函数就会执行。那么load_images 函数里干了什么事情?
void
load_images(const char *path __unused, const struct mach_header *mh)
{
recursive_mutex_locker_t lock(loadMethodLock);
// Discover +load methods
prepare_load_methods((const headerType *)mh);
// Call +load methods (without classLock - re-entrant)
call_load_methods();
}
Mach-O是OSX和iOS上的可执行二进制文件格式:Mach-Object 这里struct mach_header 是 Mach-Object 的header
prepare_load_methods 字面意思上看,就是发现类有没有实现load函数。
call_load_methods 调用上面发现的load函数。
load 的代替方案,
使用initialize 代替
initialize,可以延迟调用时机,如果app中大量使用了load 有可能导致启动慢
使用
__attribute__
<code>attribute</code> 是GNU C特色之一,在iOS用的比较广泛。如果你没有用过,那系统库你总用过,在Foundation.framework中有很多地方用到attribute特性。attribute 可以设置函数属性(Function Attribute )、变量属性(Variable Attribute )和类型属性(Type Attribute)
比如我们下面的代码
@interface TDDObj : NSObject
@end
@implementation TDDObj
+ (void)load {
NSLog(@"1111");
}
@end
__attribute((constructor)) static void beforeMainCall(void) {
NSLog(@"11111");
}
int main(int argc, char * argv[]) {
@autoreleasepool {
@try
{
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
@catch(NSException* exception)
{
PTVLog(@"exception:%@", exception);
}
}
}
我们通过断点或者观察输出log,可以看到,TDDObj 的 load 函数先调用,然后 beforeMainCall 调用,接着,main 函数调用了。
通过这种手段,可以做一些有趣的事情,比如遍历所有类,如果实现了谋改革协议或者方法做些事情,如果所有的类load里面做完事情,要进一步在 main 之前处理,就可以通过这种手段。