dyld & objc的关联
抛出问题
上回说到iOS应用程序加载大致流程分析,我们大致了解到:dyld 链接所有的库,加载到内存,生成mach-O格式的文件,但是类里面的方法、属性、协议等信息是什么时候加载到内存里的呢,说白了,就是如何加载到底层objc_class结构体
的class_rw_t *data()
里的?
找关联
在dyld::main
函数流程中,有一步initializeMainExecutable(主程序表初始化)
时,跟着调用链走到recursiveInitialization
,如下图:
其中第3点,通过回调的方式告知初始化完成:
context.notifySingle(dyld_image_state_initialized, this, NULL);
再看notifySingle
实现:
最终是调用(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
这个函数,那么接下来的目标就是sNotifyObjCInit
,它是在哪里赋值的?
全局搜索发现:
在registerObjCNotifiers
中,继续跟着调用链向上查找,最终找到_dyld_objc_notify_register
。
接着全局搜_dyld_objc_notify_register
,在dyld库源码搜不到,说明不是在dyld库调用的,再回到类何时加载到内存这个问题,既然是类的加载,类是什么-->是对象,是object
,那么我们去到objc源码里搜,果然搜到了,如图⬇️
综上我们发现,在dyld链接库的执行过程中,即在dyld::main()
的函数流程中的初始化主程序表
这一步时,会调用sNotifyObjCInit
这个回调方法,而回调方法sNotifyObjCInit
的注册是在objc
源码的_objc_init()
函数中实现的-->_dyld_objc_notify_register(&map_images, load_images, unmap_image)
,根据入参对应的位置,sNotifyObjCInit
是第二个入参,对应到_dyld_objc_notify_register
的第二个入参,最终 sNotifyObjCInit == load_images
。
再看看其它两个入参map_images
和 unmap_image
,看看是在哪里触发回调的。
registerObjCNotifiers源码如下:
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
// record functions to call
sNotifyObjCMapped = mapped;
sNotifyObjCInit = init;
sNotifyObjCUnmapped = unmapped;
// call 'mapped' function with all images mapped so far
try {
notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
}
catch (const char* msg) {
// ignore request to abort during registration
}
// <rdar://problem/32209809> call 'init' function on all images already init'ed (below libSystem)
for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it != sAllImages.end(); it++) {
ImageLoader* image = *it;
if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() ) {
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
}
}
}
map_images的回调触发
先看看map_images
,即sNotifyObjCMapped
。一样全局搜索,
只有唯一一处地方调用,在函数
notifyBatchPartial
里,同理全局搜索:第一处
第二处
第三处
第三处就是将map_images赋给sNotifyObjCMapped后就直接调用了。第二处是在registerImageStateBatchChangeHandler
函数中,全局搜索,没有地方调用,暂不考虑。剩下就是第一处,在函数notifyBatch
中,全局搜索,如图⬇️
一共5个地方调用:
- 在函数
runAllStaticTerminators
中,继续向上搜索,发现是在函数initializeMainExecutable
中,这不就是在dyld::_main()
里的初始化主程序表这步,一处关联。 - 也是在
dyld::_main()
里,在绑定主程序表的注册结果后,将结果回调给objc那边,一处关联。
3.在ImageLoader::link
链接这一步,明显也是dyld里面,一处关联。
4.在函数runInitializers
里
继续向上搜索,依旧是在initializeMainExecutable()
中,又一处关联。
5.在函数ImageLoaderMegaDylib::dlopenFromCache
里
继续向上搜索,发现调用方是函数dyld::dlopenFromCache
。
继续查看dyld::dlopenFromCache
,在函数dyld::dlopen_internal
里调用,但都是在 if 判断语句里,不是重点分析对象,但也是dyld与objc的一处关联地方。
综上,我们查找map_images的调用,总共搜寻到7处关联。
unmap_image的回调触发
最后查看unmap_image
,即sNotifyObjCUnmapped
。一样全局搜索,
在函数
dyld::removeImage
里触发,即移除镜像文件时。这也是一处关联。
总结
我们在dyld::_main()
函数的流程里,在初始化主程序表initializeMainExecutable
的过程中,沿着调用链顺序向下查找到recursiveInitialization
,查看其源码知道了在主程序表完成初始化后,通过回调notifySingle
对外通知完成状态,接着我们再沿着调用链向上搜索notifySingle
,最终发现_dyld_objc_notify_register
,在这里完成了回调函数的赋值操作,接着再查询到_dyld_objc_notify_register
是在objc源码的_objc_init
里调用的,进而知道了dyld与objc是相互关联的,在objc里注册回调函数,在dyld里触发回调函数。
接着我们重点查看_dyld_objc_notify_register
函数里三个函数map_images load_images unmap_image
的调用链,找到了9处关联的地方。