前言
上一篇博客我们了解到dyld->libsystem->libdispatch->_objc_init
这样一个流程。那么下面我们来了解一下objc_init
里面究竟做了些啥。
objc_init分析
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);
}
还可以通过在终端里面输入export OBJC_HELP=1
,然后再输入lldb
:打印信息如下
我们可以在
xcode
里面设置一下OBJC_PRINT_IMAGES
设置一下这个环境变量,就会打印出。通过Edit Scheme ->Run
就可以设置。
tls_init
:关于线程key的绑定,比如每个现场的析构函数static_init
:运⾏C ++静态构造函数。在dyld调⽤之前自己先调用runtime_init
:主要是初始化unattachedCategories
和allocatedClasses
两个表。exception_init
:异常初始化,我们常用的objc_setUncaughtExceptionHandler
处理崩溃后异常,或奔溃日志上传服务器。cache_t::init()
缓存条件初始化_imp_implementationWithBlock_init
:启动回调机制。通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib._dyld_objc_notify_register
,将map_images
load_images
unmap_image
方法赋值给dyld去触发调用
map_images分析
map_images_nolock
这个方法主要是找到macho
所有的image
,然后调用_read_images
_read_images分析
这个里面,代码有很多,当时苹果工程师做了很多的日志打印。我们可以通过这个来分成小模块去分析。
条件控制进行第一次加载
创建NXMapTable
表,大小为4/3
*totalClasses
。这个表是一张class
总表;与上面的allocatedClasses
表是有区别的,后面的是存储已经alloc
的。修复预编译阶段的 @selector 的混乱问题
. 为什么会需要修复呢?这是因为,我们从image
里面获取的方法的地址是没有加上ASLR
也就是基地址,需要通过dyld
加上基地址后重新修复。读取类readClass、处理没有被清理的类
修复重映射一些没有被镜像文件加载进来的类remapClassRef
修复一些消息fixupMessageRef
读取类的协议readProtocol
修复没有被加载的协议remapProtocolRef
加载分类load_categories_nolock
加载类realizeClassWithoutSwift
实现新解析的未来类
下面我们主要分析一下readClass
readClass分析
在readClass
方法中添加如下代码,这样就可以研究我们自定义的类是如何读取的。
断点继续执行:
发现并没有执行ro,rw相关的赋值。
断点继续下一步,会调用
addNamedClass
,
这时会把
class
插入到总表中
- 继续执行会递归调用
addClassTableEntry
,
addClassTableEntry(Class cls, bool addMeta = true)
{
runtimeLock.assertLocked();
// This class is allowed to be a known class via the shared cache or via
// data segments, but it is not allowed to be in the dynamic table already.
auto &set = objc::allocatedClasses.get();
ASSERT(set.find(cls) == set.end());
if (!isKnownClass(cls))
set.insert(cls);
if (addMeta)
addClassTableEntry(cls->ISA(), false);
}
把class
插入到allocatedClasses
表中,如果有元类,把元类也插入进去。
类的实现realizeClassWithoutSwift
- 新建一个
QHPerson
类,然后在里面添加load
方法,在代码里面添加如下代码,这样就可以研究我们自定义的类
继续执行进入到
realizeClassWithoutSwift
方法。
- ro 赋值给rw
- 设置父类和元类关系
- 条理化class
methodizeClass分析
下面我们继续进入methodizeClass
方法,添加如下代码,这里多了一个元类的判断,因为元类和本身类的名字一样:
打印rwe:发现为nil
- 证明
rwe->properties.attachLists(&proplist, 1);
和rwe->protocols.attachLists(&protolist, 1);
。 - 如果有方法就会调用
prepareMethodLists
,这个方法实际上是通过地址对方法进行排序,下面的代码可以验证
- 将分类添加到主类
attachToClass
,这个方法下篇博客再分析 - 所以
methodizeClass
主要是方法排序和将分类添加到主类中。
懒加载和非懒加载
上面我们在自定义的类中添加了load方法,现在我们去掉.然后再运行。发现一个奇怪的现象:
这是为什么???下面我们在
main
里面调用QHPerson
的方法。继续运行,这个时候进入了,只不过不是走的之前的流程:
通过堆栈,可以看出是在main函数之后走的消息发送的流程。
总结
- 如果自定义的类实现了
load
方法,(非懒加载
),会通过dyld->objc_init->(实例化主程序)->read_image->read_class
去加载类。 - 如果没有实现
load
方法,(懒加载
),会在第一次调用对象方法的时候去,走消息转发流程
去实现 懒加载
这样的处理方式会大大优化app的启动时间
。