dyld和objc的关联

在上一篇文章中,我们知道了dyld是苹果的动态链接器,以及讲到了链接镜像文件和整个的加载流程。 那么dyld是怎么和objc进行关联的呢,这篇文章就来了解一下。

dyld在加载的时候会加载objc,加载objc就会进行初始化来到_objc_init。我们先简单的来看一下_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);
    }
设置isa的环境变量

我们可以把OBJC_DISABLE_NONPOINTER_ISA这个环境变量设置成YES,设置之后non-pointer就为0了,生成都是普通的isa, 不再是non-pointer1优化过的isa了。

同时,我们还可以设置OBJC_PRINT_LOAD_METHODS为YES,设置完运行之后就能打印所有用到load方法的地方

load方法环境变量设置
控制台打印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源码进行全局搜索

_dyld_objc_notify_register

_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里面

registerObjCNotifiers

这里把_objc_init里面的
map_images赋值给了sNotifyObjCMapped
load_images赋值给了sNotifyObjCInit
我们再找一下sNotifyObjCMappedsNotifyObjCInit

sNotifyObjCMapped
sNotifyObjCInit

由此可见,_objc_init里面的map_imagesload_images在这两个地方进行了调用执行。这就形成了一个关联,objcdyld之间是相互来往的。

关联的的流程我在上一篇文章中也讲到过,接下来再来总结一下:

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

来到map_images_nolock里面后找重点,找到了594行,如果镜像数量大于0,就开始读我们所有的镜像文件

_read_images

来到这里之后代码非常之多, 直接看的话可能会摸不着头尾,先用文字进行总结一下,把大致的流程先过一遍,先从整体再到局部:

1、 条件控制进行一次的加载

2、 修复预编译阶段的 @selector 的混乱问题

3、错误混乱的类处理

4、修复重映射一些没有被镜像文件加载进来的 类

5、 修复一些消息!

6、 当我们类里面有协议的时候 : readProtocol

7、 修复没有被加载的协议

8、 分类处理

9、 类的加载处理

10 、没有被处理的类 优化那些被侵犯的类

了解了read_images的整体流程之后,下一篇文章我们将对里面的class Protocol selector category进行展开分析,敬请期待。

iOS 底层原理 文章汇总

你可能感兴趣的:(dyld和objc的关联)