在上一篇文章中,我们知道了dyld
是苹果的动态链接器,以及讲到了链接镜像文件和整个的加载流程。 那么dyld
是怎么和objc进行关联的呢,这篇文章就来了解一下。
dyld
在加载的时候会加载objc
,加载objc
就会进行初始化来到_objc_init
。我们先简单的来看一下_objc_init
里面有些什么
static bool initialized = false;
if (initialized) return;
initialized = true;
这一部分代码就是判断是否初始化的条件,初始化了就return
。
往下走来到我们的environ_init
,环境变量的初始化,里面主要是读取影响运行时的环境变量。我们打印出环境变量帮助,来玩一下。
for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
const option_t *opt = &Settings[I];
_objc_inform("%s: %s", opt->env, opt->help);
_objc_inform("%s is set", opt->env);
}
我们可以把OBJC_DISABLE_NONPOINTER_ISA
这个环境变量设置成YES,设置之后non-pointer
就为0
了,生成都是普通的isa, 不再是non-pointer
为1
优化过的isa了。
同时,我们还可以设置OBJC_PRINT_LOAD_METHODS
为YES,设置完运行之后就能打印所有用到load
方法的地方
这样你想找某个方法在哪些类里面使用了就非常方便了。还有很多环境变量可以玩,就不一一列举了,介绍几个常用的环境变量
第一个函数清楚之后,下面的函数简单了解一下:
2、tls_init()
:关于线程的处理。线程key的绑定 - 比如线程数据的析构函数
3、static_init
: 运行C ++静态构造函数。在dyld调用我们的静态构造函数之前,libc
会调用_objc_init()
,因此我们必须自己做
4、runtime_init
: runtime运行时环境初始化,里面主要是:unattachedCategories,allocatedClasses
后面会具体展开分析
5、 exception_init()
:初始化libobjc的异常处理系统
6、cache_init
: 缓存条件初始化
7、_imp_implementationWithBlock_init
:启动回调机制。通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加trampolines dylib
。
8:_dyld_objc_notify_register(&map_images, load_images, unmap_image);
:这一句就是我们要重点关注的,接下来进入今天的主题
dyld和objc关联
我们在objc4-787源码工程下发现点不进去_dyld_objc_notify_register
,看过我上一篇文章的应该知道是啥情况,此时我们需要打开dyld
源码进行全局搜索
在_objc_init
里面调用_dyld_objc_notify_register
相当于调到了dyld
底层里面的_dyld_objc_notify_register
,这个地方进行了跨库调用。
接下来就好办了,
dyld
里面的参数mapped
相当于_objc_init
里面的map_images
,
dyld
里面的参数init
相当于_objc_init
里面的load_images
,
dyld
里面的参数unmapped
相当于_objc_init
里面的unmap_image
,
我们再点到registerObjCNotifiers
里面
这里把_objc_init
里面的
map_images
赋值给了sNotifyObjCMapped
load_images
赋值给了sNotifyObjCInit
我们再找一下sNotifyObjCMapped
和sNotifyObjCInit
由此可见,_objc_init
里面的map_images
和load_images
在这两个地方进行了调用执行。这就形成了一个关联,objc
和dyld
之间是相互来往的。
关联的的流程我在上一篇文章中也讲到过,接下来再来总结一下:
1、在dyld::_main()
函数的流程里,初始化主程序表initializeMainExecutable
2、初始化完了之后来到recursiveInitialization
,通过回调notifySingle
对外通知完成状态
3、在notifySingle
里面的registerObjCNotifiers
完成了赋值操作,registerObjCNotifiers
是在_dyld_objc_notify_register
调用的
4、_dyld_objc_notify_register
又是在objc源码里面的_objc_init
里面调用的。从而得出在objc里面注册回调函数,在dyld里面触发回调函数。
下一篇文章预告
我们来看看map_images
里面做了什么,点进去
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
mutex_locker_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
再点到map_images_nolock
里面
来到map_images_nolock
里面后找重点,找到了594
行,如果镜像数量大于0,就开始读我们所有的镜像文件
来到这里之后代码非常之多, 直接看的话可能会摸不着头尾,先用文字进行总结一下,把大致的流程先过一遍,先从整体再到局部:
1、 条件控制进行一次的加载
2、 修复预编译阶段的 @selector
的混乱问题
3、错误混乱的类处理
4、修复重映射一些没有被镜像文件加载进来的 类
5、 修复一些消息!
6、 当我们类里面有协议的时候 : readProtocol
7、 修复没有被加载的协议
8、 分类处理
9、 类的加载处理
10 、没有被处理的类 优化那些被侵犯的类
了解了read_images
的整体流程之后,下一篇文章我们将对里面的class Protocol selector category
进行展开分析,敬请期待。