前言
上一个篇章, 从dyld
→libSystem
→libDispatch
→libObjc
调用起了_objc_init
然后调用到了一个回调函数. 然后在回调函数对dyld需要的参数进行赋值, 通知, dyld进行加载.
那么今天就来看一看_objc_init
到底做了什么
_objc_init
_objc_init
一般看比较标准的源码, 或者三方库, 优先看注释:
- 引导程序初始化, 使dyld注册我们的镜像通知
- 由
libSystem BEFORE library
初始化时间调用
上面两句话说明白了我们之前的流程, 由dyld注册通知, 也就是这里是通知发起的地方, dyld在注册的地方进行调用. 在libSystem
初始化的时候就会调用我们的_objc_init
.
下面我们看看程序的初始化都做了点什么事情:
void _objc_init(void)
{
//先看看这里, 只会初始化一次.
static bool initialized = false;
if (initialized) return;
initialized = true;
// fixme defer initialization until an objc-using image is found?
//修复延迟初始化, 直到找到使用objc的镜像
environ_init();
tls_init();
static_init();
runtime_init();
exception_init();
#if __OBJC2__
cache_t::init();
#endif
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
environ_init
环境变量初始化
void environ_init(void) {
if (PrintHelp || PrintOptions) {
if (PrintHelp) {
_objc_inform("Objective-C runtime debugging. Set variable=YES to enable.");
_objc_inform("OBJC_HELP: describe available environment variables");
if (PrintOptions) {
_objc_inform("OBJC_HELP is set");
}
_objc_inform("OBJC_PRINT_OPTIONS: list which options are set");
}
if (PrintOptions) {
_objc_inform("OBJC_PRINT_OPTIONS is set");
}
for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
const option_t *opt = &Settings[i];
if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env);
}
}
}
可以看出是一些环境变量的打印:
- 可以直接赋值打印代码, 去除条件控制, 在函数内打印.(有可编译源码)
- 终端直接
export OBJC_HELP=1
-
objc-env.h
可以直接查看
当做工具使用就行, 不需要去记忆.OBJC_PRINT_LOAD_METHODS
设置为YES, 可以打印出项目中所有的load
方法调用, 还是挺实用的.
tls_init
初始化线程, key和线程绑定, 方便调度使用.
static_init
静态函数的调用, libc
自己的静态函数, 调用以及符号的更换个绑定. 到dyld
调用的时候, 就是已经替换的符号了
runtime_init
runtime的初始化
初始化两张表, 后续了解
void runtime_init(void)
{
//分类表
objc::unattachedCategories.init(32);
//已经开辟内存的对象所在的表
objc::allocatedClasses.init();
}
exception_init
libc
的异常处理系统初始化
/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system.
* Called by map_images().
初始化异常处理系统, 被map_images()调用. 也就是说这里并不被调用, 只是赋值
**********************************************************************/
void exception_init(void)
{
old_terminate = std::set_terminate(&_objc_terminate);
}
/***********************************************************************
* _objc_terminate
* Custom std::terminate handler.
*
* The uncaught exception callback is implemented as a std::terminate handler.
* 1. Check if there's an active exception //检查是否有活动异常
* 2. If so, check if it's an Objective-C exception //如果是, 请检查是否是OC异常
* 3. If so, call our registered callback with the object.//如果是, 请使用对象调用我们的注册的回调
* 4. Finally, call the previous terminate handler. //最后,调用前一个终止处理程序
**********************************************************************/
static void (*old_terminate)(void) = nil;
static void _objc_terminate(void)
{
if (PrintExceptions) {
_objc_inform("EXCEPTIONS: terminating");
}
if (! __cxa_current_exception_type()) {
// No current exception.
(*old_terminate)();
}
else {
// There is a current exception. Check if it's an objc exception.
@try {
__cxa_rethrow();
} @catch (id e) {
// It's an objc object. Call Foundation's handler, if any.
(*uncaught_handler)((id)e);
(*old_terminate)();
} @catch (...) {
// It's not an objc object. Continue to C++ terminate.
(*old_terminate)();
}
}
}
上面注释里面, 第三步注释说明了, 如果是OC异常, 使用OC对象注册一个异常回调来处理这个事情. (*uncaught_handler)((id)e);
重点关注下个OC
异常的代码就可以.
cache_t::init
缓存条件初始化。
_imp_implementationWithBlock_init
初始化Trampolines
, 通常不需要做什么, 初始化是懒加载的. 但是对于某些进程我们会急于去加载trampolines
库.
根据宏看是macos才加载的, 不是重点, 无需关注.
_dyld_objc_notify_register
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
这里回调函数是重点, 之前dyld
也说了真实的调用在dyld
之中, 这里做了一个赋值操作. 在这里:
-
map_images
:管理了文件和库中的所有符号. 传入的是地址指针, 保证这部分信息的同步性, 防止信息错乱而发生错误. -
load_images
: 加载load方法, 传入的是值, 调用可以直接实现. -
unmap_image
: 取消镜像的映射
≈
void
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);
}
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
if (firstTime) {//如果是第一次架子啊
sel_init(selrefCount); //C++析构函数
//1.autoreleasePage的init调用
//2.初始化一个SideTablesMap的表(内部含有weak表, 引用计数表等等)
//3.初始化关联对象的表
arr_init();
}
if (hCount > 0) {
//核心重点
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
}
接下来我继续看_read_images
先看整体流程, 下面在分开看.
整体分析:
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
static bool doneOnce;
// 1.条件控制,进行一次的加载。将所有类放入一个表中
if (!doneOnce) {
doneOnce = YES;
.....
}
// Fix up @selector references
// 2.修复预编译阶段的 `@selector` 的混乱问题
// macho加载进来的地址要以dyld链接的内存地址为准
static size_t UnfixedSelectors;
{ ... }
ts.log("IMAGE TIMES: fix up selector references");
// Discover classes. Fix up unresolved future classes. Mark bundle classes.共享缓存
bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
// 3.修复错误的类
for (EACH_HEADER) { ... }
ts.log("IMAGE TIMES: discover classes");
// 4.修复重映射⼀些没有被镜像⽂件加载进来的 类
if (!noClassesRemapped()) { ... }
ts.log("IMAGE TIMES: remap classes");
#if SUPPORT_FIXUP
// 5.修复一些消息
// Fix up old objc_msgSend_fixup call sites
for (EACH_HEADER) { ... }
ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
#endif
bool cacheSupportsProtocolRoots = sharedCacheSupportsProtocolRoots();
// Discover protocols. Fix up protocol refs.
// 6.发现协议
for (EACH_HEADER) { ... }
ts.log("IMAGE TIMES: discover protocols");
// 7.修复没有被加载的协议
for (EACH_HEADER) { ... }
ts.log("IMAGE TIMES: fix up @protocol references");
// 8.分类处理
if (didInitialAttachCategories) { ... }
ts.log("IMAGE TIMES: discover categories");
// 9.懒加载类的处理 类实现
// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) { ... }
ts.log("IMAGE TIMES: realize non-lazy classes");
// 10.实现新解析的未来类,以防 CF框架操作它们
// Realize newly-resolved future classes, in case CF manipulates them
if (resolvedFutureClasses) { ... }
ts.log("IMAGE TIMES: realize future classes");
if (DebugNonFragileIvars) {
realizeAllClasses();
}
}
1. doneOnce第一次加载
//全局只会走一次根据静态变量doneOnce控制
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
创建一张类表, 包含所有的类. 负载因子为3/4, 相当于哈希表反向的计算. 所以gdb_objc_realized_classes
这是一张哈希表, 存储了所有的类(不管实现未实现)
2. UnfixedSelectors修复方法
SEL *sels = _getObjc2SelectorRefs(hi, &count);
UnfixedSelectors += count;
for (i = 0; i < count; i++) {
const char *name = sel_cname(sels[i]);
//sel_registerNameNoLock → __sel_registerName → search_builtins → _dyld_get_objc_selector
//从dyld中加载进来的sel链接地址
SEL sel = sel_registerNameNoLock(name, isBundle);
if (sels[i] != sel) {
sels[i] = sel;
}
}
_getObjc2SelectorRefs
是从macho里面加载的sel
sel_registerNameNoLock
是_dyld_get_objc_selector
从dyld链接加载的可以访问的内存地址的方法, 修复macho加载出来的方法地址定向.
3.类的处理
在此部分会初始化类的名称。类已移动但未删除,对错误混乱的类进行处理。readClass会对类做一些处理.
(lldb) po cls
objc[43705]: mutex incorrectly locked
objc[43705]: mutex incorrectly locked
0x0000000100409fe8
(lldb) po cls
objc[43705]: mutex incorrectly locked
objc[43705]: mutex incorrectly locked
OS_object
(lldb) po newCls
objc[43705]: mutex incorrectly locked
objc[43705]: mutex incorrectly locked
OS_object
上面是断点打印, 可以发现for循环内部不会走, 经历readClass
前后打印cls
发生了变化. readClass
整个流程是_read_images
→readClass
→addNamedClass:
→gdb_objc_realized_classes
把类插入表里(name, cls)
→addClassTableEntry
插元类.
终止条件为,allocatedClasses
表里已存在, 所以元类不会一直加载.
4.重映射类remap classes
- 修复重映射的类
- 类列表和非懒加载类列表保持未重新映射。
- 类引用和super引用被重新映射用于消息调度。
5.消息修复fixupMessageRef
llvm阶段已经做了, 在这里不会走的. 可以看一下, 哪些方法需要检查修复:
msg->sel = sel_registerName((const char *)msg->sel);
if (msg->imp == &objc_msgSend_fixup) {
if (msg->sel == @selector(alloc)) {
msg->imp = (IMP)&objc_alloc;
} else if (msg->sel == @selector(allocWithZone:)) {
msg->imp = (IMP)&objc_allocWithZone;
.....只看几个, 就知道了, llvm阶段会做一些符号的修改, 所以我们在之前篇章的时候, `alloc`调用的符号竟然是`objc_alloc`而疑惑了好久
6.发现协议
简要来说就是将协议插入协议表protocols()
7.修复协议的引用
8.Discover categories.
load_categories_nolock
分类的加载, 在dyld调用完成之后才会调用, 之类不会走到, 后面会了解到分类.
你会发现上面都是Discover xxx
之后会Fix up xxx
, 这里分类却没有, 所以分类的加载, 后续在讨论
9.懒加载类的调用
// Realize non-lazy classes (for +load methods and static instances)
注释写到, 实现非懒加载的类, 调用load
方法和静态实例.
在3流程中调用过readClass
里面的整个流程. 所以
for (EACH_HEADER) {
//从非懒加载表里取出, 非懒加载类
classref_t const *classlist = hi->nlclslist(&count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
//3流程中走过`readImage`里面加入了表里, 所以这里不会走
addClassTableEntry(cls);
//在这里进行实现
realizeClassWithoutSwift(cls, nil);
}
}
- 非懒加载类在这里进行实现.
-
_read_images
→
realizeClassWithoutSwift
→methodizeClass
- 懒加载类, 在进行第一次消息调用的时候会走到消息慢速查找的时候实现.
-
lookUpImpOrForward
→
realizeAndInitializeIfNeeded_locked
→
realizeClassMaybeSwiftAndLeaveLocked
→
realizeClassMaybeSwiftMaybeRelock
→
realizeClassWithoutSwift
→methodizeClass
10.realize future classes
实现未来类, 以防止CF操作他们, 调试了一下 ,没有进入到这里. 不关注了.
readImage
readImage
的核心流程
-
addNamedClass
的时候将name
和cls
加入gdb_objc_realized_classes
表里, 并且关联地址 -
addClassTableEntry
的时候将类以及ISA走向的类递归加入allocatedClasses
表中
所以readClass
的核心逻辑是将类与地址关联,并加入gdb_objc_realized_classes
与allocatedClasses
表中
- 调用
runtime_init()
初始化了allocatedClasses
和unattachedCategories
表. -
_read_images
的doneOnce
流程中初始化了gdb_objc_realized_classes
这张表.
总结:
objc_init
:
environ_init();
tls_init();
static_init();
runtime_init();
exception_init();
cache_t::init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
map_images
:
-
doneOnce
创建类的总表, 等一些只进行一次的操作. -
UnfixedSelectors
修复混乱的方法 -
discover classes
发现类, 调用了readImage
-
remap classes
类的重映射 -
objc_msgSend_fixup
消息修复 -
discover protocols
协议加进协议表里 -
fix up @protocol references
协议的引用修复 -
discover categories
分类处理, 不会走到这里, 注释已经说明了, 在回调函数走完之后才可以. -
realize non-lazy classes
实现非懒加载的类,realizeClassWithoutSwift
实现的主要方法, 核心在于load
方法的实现. -
realize future classes
不是重点, 不关注.