前文dyld加载流程中说到,dyld在初始化的时候会先初始化libsystem,然后是libdispatch、libobjc;在libobjc的_objc_init中通过调用_dyld_objc_notify_register函数将map_images, load_images, unmap_image三个函数回调给dyld来调用。
本文聚焦于前两个函数,也就是加载类的流程。在看加载类的流程之前,需要先做一些准备工作,可以看下_objc_init函数做了哪些准备工作:
_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?
//环境变量初始化
environ_init();
//创建线程的析构函数 设置线程key的绑定
tls_init();
//运行c++的静态构造函数
static_init();
//初始化runtime运行环境 初始化两张表-分类的表的初始化-类的表的初始化
runtime_init();
//异常处理的初始化
exception_init();
#if __OBJC2__
//关于缓存的初始化
cache_t::init();
#endif
//MAC-OSX 处理初始化异常处理回调
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
可以看到_objc_init也主要是在做初始化的相关操作,包括环境变量、线程tls设置、runtime、cache等等,如下所示:
environ_init()环境变量初始化;tls_init()创建线程的析构函数,处理线程key的绑定;static_init()运行c++的静态构造函数;runtime_init()初始化两张表-分类的表的初始化-类的表的初始化;exception_init()异常处理的初始化;didCallDyldNotifyRegister标识对_dyld_objc_notify_register的调⽤已完成。;
environ_init
environ_init是读取environment variables的一些配置信息,environment variables在Edit Scheme -> Run -> Argments -> Environment Variables 中配置。
上方源码中PrintOptions和PrintHelp参数可以通过在environment variables中设置OBJC_PRINT_OPTIONS和OBJC_HELP来更改值:
OBJC_PRINT_OPTIONS是否输出OBJC已设置的选项OBJC_HELP输出OBJC已设置的选项下面我们尝试添加几个环境变量看下效果:
OBJC_DISABLE_NONPOINTER_ISA
OBJC_DISABLE_NONPOINTER_ISA为判断是否开启指针优化。YES表示纯指针,NO就是使用nonpointer isa。
- 未设置
OBJC_DISABLE_NONPOINTER_ISA,对象的isa地址的二进制打印,末尾为1,表示isa不只是类对象地址,isa中包含了类信息、对象的引用计数等 - 设置
OBJC_DISABLE_NONPOINTER_ISA环境变量为YES后,末尾变成了0,此时isa就是类的首地址,是一个纯的isa如下图:
OBJC_PRINT_LOAD_METHODS
OBJC_PRINT_LOAD_METHODS:打印 Class 及 Category 的 + (void)load 方法的调用信息,包括动态库中的也会被打印。所以,OBJC_PRINT_LOAD_METHODS可以监控所有的+load方法(包括动态库)
当我们在类中添加load方法后,运行程序,所有调用load函数的就会打印出来,如下图:
其他环境变量设置:
map_images
回到_dyld_objc_notify_register中,我们先看下第一个参数map_images
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);
}
map_images直接调用了map_images_nolock,map_images_nolock内容非常多,我们分段来看下:
首先是传入参数:
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
mhCount:mach-o header count,即mach-o header个数mhPaths:mach-o header Paths,即header的路径数组mhdrs:单个mhdr指的是mach-o header 的header指针
// 局部静态变量,表示第一次调用
static bool firstTime = YES;
// hList 是统计 mhdrs 中的每个 mach_header 对应的 header_info
header_info *hList[mhCount];
uint32_t hCount;
size_t selrefCount = 0;
//如有必要,执行首次初始化。
// Perform first-time initialization if necessary.
//此函数在 ordinary library 初始化程序之前调用。
// This function is called before ordinary library initializers.
// fixme defer initialization until an objc-using image is found?
// 如果是第一次加载,则准备初始化环境
if (firstTime) {
preopt_init();
}
// 开启 OBJC_PRINT_IMAGES 环境变量时,启动时则打印 images 数量。
// 如:objc[10503]: IMAGES: processing 296 newly-mapped images...
if (PrintImages) {
_objc_inform("IMAGES: processing %u newly-mapped images...\n", mhCount);
}
这一段最主要的就是preopt_init,如果是第一次加载,则准备初始化环境,那么初始化什么环境呢?我们暂且不说,我们先继续往下看下段代码:
// Find all images with Objective-C metadata.
hCount = 0;
// 计算 class 的数量。根据总数调整各种表格的大小。
// Count classes. Size various table based on the total.
int totalClasses = 0;
int unoptimizedTotalClasses = 0;
{
uint32_t i = mhCount;
while (i--) {
// 取得指定 image 的 header 指针
const headerType *mhdr = (const headerType *)mhdrs[i];
// 以 mdr 构建其 header_info,并添加到全局的 header 列表中(是一个链表,大概看源码到现在还是第一次看到链表的使用)。
// 且通过 GETSECT(_getObjc2ClassList, classref_t const, "__objc_classlist"); 读取 __objc_classlist 区中的 class 数量添加到 totalClasses 中,
// 以及未从 dyld shared cache 中找到 mhdr 的 header_info 时,添加 class 的数量到 unoptimizedTotalClasses 中。
auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
// 这里有两种情况下 hi 为空:
// 1. mhdr 的 magic 不是既定的 MH_MAGIC、MH_MAGIC_64、MH_CIGAM、MH_CIGAM_64 中的任何一个
// 2. 从 dyld shared cache 中找到了 mhdr 的 header_info,并且 isLoaded 为 true()
if (!hi) {
// no objc data in this entry
continue;
}
// #define MH_EXECUTE 0x2 /* demand paged executable file demand 分页可执行文件 */
if (mhdr->filetype == MH_EXECUTE) {
// Size some data structures based on main executable's size
#if __OBJC2__ // 根据主要可执行文件的大小调整一些数据结构的大小
// If dyld3 optimized the main executable, then there shouldn't
// be any selrefs needed in the dynamic map so we can just init
// to a 0 sized map
if ( !hi->hasPreoptimizedSelectors() ) {
size_t count;
// 获取 __objc_selrefs 区中的 SEL 的数量
_getObjc2SelectorRefs(hi, &count);
selrefCount += count;
// GETSECT(_getObjc2MessageRefs, message_ref_t, "__objc_msgrefs");
// struct message_ref_t {
// IMP imp;
// SEL sel;
// };
// 获取 __objc_msgrefs 区中的 message 数量
_getObjc2MessageRefs(hi, &count);
selrefCount += count;
}
#else
_getObjcSelectorRefs(hi, &selrefCount);
#endif
#if SUPPORT_GC_COMPAT
// Halt if this is a GC app.
if (shouldRejectGCApp(hi)) {
_objc_fatal_with_reason
(OBJC_EXIT_REASON_GC_NOT_SUPPORTED,
OS_REASON_FLAG_CONSISTENT_FAILURE,
"Objective-C garbage collection "
"is no longer supported.");
}
#endif
}
hList[hCount++] = hi;
if (PrintImages) {
_objc_inform("IMAGES: loading image for %s%s%s%s%s\n",
hi->fname(),
mhdr->filetype == MH_BUNDLE ? " (bundle)" : "",
hi->info()->isReplacement() ? " (replacement)" : "",
hi->info()->hasCategoryClassProperties() ? " (has class properties)" : "",
hi->info()->optimizedByDyld()?" (preoptimized)":"");
}
}
}
这又是个大段篇幅,我们逐步分析:
(const headerType *)mhdrs[i] 取得指定 image 的 header 指针,这里也是将mach_header转换为headerType指针,然后就是addHeader方法。
addHeader
static header_info * addHeader(const headerType *mhdr, const char *path, int &totalClasses, int &unoptimizedTotalClasses)
{
header_info *hi;
if (bad_magic(mhdr)) return NULL;
bool inSharedCache = false;
// Look for hinfo from the dyld shared cache.
hi = preoptimizedHinfoForHeader(mhdr);
if (hi) {
// Found an hinfo in the dyld shared cache.
// Weed out duplicates.
if (hi->isLoaded()) {
return NULL;
}
inSharedCache = true;
// Initialize fields not set by the shared cache
// hi->next is set by appendHeader
hi->setLoaded(true);
//部分代码省略
}
else
{
// Didn't find an hinfo in the dyld shared cache.
// Locate the __OBJC segment
size_t info_size = 0;
unsigned long seg_size;
const objc_image_info *image_info = _getObjcImageInfo(mhdr,&info_size);
const uint8_t *objc_segment = getsegmentdata(mhdr,SEG_OBJC,&seg_size);
if (!objc_segment && !image_info) return NULL;
// Allocate a header_info entry.
// Note we also allocate space for a single header_info_rw in the
// rw_data[] inside header_info.
hi = (header_info *)calloc(sizeof(header_info) + sizeof(header_info_rw), 1);
// Set up the new header_info entry.
hi->setmhdr(mhdr);
#if !__OBJC2__
// mhdr must already be set
hi->mod_count = 0;
hi->mod_ptr = _getObjcModules(hi, &hi->mod_count);
#endif
// Install a placeholder image_info if absent to simplify code elsewhere
static const objc_image_info emptyInfo = {0, 0};
hi->setinfo(image_info ?: &emptyInfo);
hi->setLoaded(true);
hi->setAllClassesRealized(NO);
}
#if __OBJC2__
{
size_t count = 0;
if (_getObjc2ClassList(hi, &count)) {
totalClasses += (int)count;
if (!inSharedCache) unoptimizedTotalClasses += count;
}
}
#endif
appendHeader(hi);
return hi;
}
这里addHeader函数大体上分为一下几步
- 判断一下
当前的header在dyld的共享缓存中有没有 - 如果有的话直接设置已加载
- 如果共享缓存中没有,那么就实行“
封装操作” - 封装成功以后,加入到
链表中。 其中preoptimizedHinfoForHeader(mhdr)为判断当前的header在dyld的共享缓存中有没有:
header_info *preoptimizedHinfoForHeader(const headerType *mhdr)
{
#if !__OBJC2__
// fixme old ABI shared cache doesn't prepare these properly
return nil;
#endif
objc_headeropt_ro_t *hinfos = opt ? opt->headeropt_ro() : nil;
if (hinfos) return hinfos->get(mhdr);
else return nil;
}
objc_opt_t
查看源码可以看到是通过opt来获取数据,也就是说共享缓存的数据都存放在opt内,通过源码可进一步查看opt类型,其为静态变量,~0就是0Xffffffff,这是块安全区域。
static const objc_opt_t *opt = (objc_opt_t *)~0;
objc_opt_t结构体内部存储的数据大体上有一下几种
- 类的缓存
clsopt() - 协议的缓存
protocolopt() - 头的缓存
headeropt_rw() - 选择器的缓存
selopt()
struct alignas(alignof(void*)) objc_opt_t {
const objc_selopt_t* selopt() const {
if (selopt_offset == 0) return NULL;
return (objc_selopt_t *)((uint8_t *)this + selopt_offset);
}
objc_selopt_t* selopt() {
if (selopt_offset == 0) return NULL;
return (objc_selopt_t *)((uint8_t *)this + selopt_offset);
}
struct objc_headeropt_ro_t* headeropt_ro() const {
if (headeropt_ro_offset == 0) return NULL;
return (struct objc_headeropt_ro_t *)((uint8_t *)this + headeropt_ro_offset);
}
struct objc_clsopt_t* clsopt() const {
if (clsopt_offset == 0) return NULL;
return (objc_clsopt_t *)((uint8_t *)this + clsopt_offset);
}
struct objc_protocolopt_t* protocolopt() const {
if (unused_protocolopt_offset == 0) return NULL;
return (objc_protocolopt_t *)((uint8_t *)this + unused_protocolopt_offset);
}
struct objc_headeropt_rw_t* headeropt_rw() const {
if (headeropt_rw_offset == 0) return NULL;
return (struct objc_headeropt_rw_t *)((uint8_t *)this + headeropt_rw_offset);
}
};
回到addHeader函数中,如果hi有值,那么设置已加载,如果没有值,则进行封装,然后就是往链表中添加数据:
FirstHeader为链表的头节点LastHeader为链表的尾节点
void appendHeader(header_info *hi)
{
// Add the header to the header list.
// The header is appended to the list, to preserve the bottom-up order.
hi->setNext(NULL);
if (!FirstHeader) {
// list is empty
FirstHeader = LastHeader = hi;
} else {
if (!LastHeader) {
// list is not empty, but LastHeader is invalid - recompute it
LastHeader = FirstHeader;
while (LastHeader->getNext()) LastHeader = LastHeader->getNext();
}
// LastHeader is now valid
LastHeader->setNext(hi);
LastHeader = hi;
}
#if __OBJC2__
if ((hi->mhdr()->flags & MH_DYLIB_IN_CACHE) == 0) {
foreach_data_segment(hi->mhdr(), [](const segmentType *seg, intptr_t slide) {
uintptr_t start = (uintptr_t)seg->vmaddr + slide;
objc::dataSegmentsRanges.add(start, start + seg->vmsize);
});
}
#endif
}
header_info
static header_info * addHeader(const headerType *mhdr, const char *path, int &totalClasses, int &unoptimizedTotalClasses)
最后我们看到该函数返回的是一个header_info的数据,header_info既是个结构体也是个链表(代码过长不粘了),通过getNext来获取链表的下一节点数据,setNext(header_info *v)设置节点数据,头节点为FirstHeader;
preopt_init
到这里我们就知道第一步的preopt_init是做什么的了,也就是初始化共享缓存的数据,包括类、协议、头部信息、选择器等
接下来就是根据获取images中的 SEL 的数量,然后进入如下代码:
if (firstTime) {
// 初始化 selector 表并注册内部使用的 selectors。
sel_init(selrefCount);
//这里的 arr_init 函数超重要,可看到它内部做了三件事:
// 1. 自动释放池的初始化(实际是在 TLS 中以 AUTORELEASE_POOL_KEY 为 KEY 写入 tls_dealloc 函数(自动释放池的销毁函数:内部所有 pages pop 并 free))
// 2. SideTablesMap 初始化,也可理解为 SideTables 的初始化(为 SideTables 这个静态全局变量开辟空间)
// 3. AssociationsManager 的初始化,即为全局使用的关联对象表开辟空间
// void arr_init(void)
// {
// AutoreleasePoolPage::init();
// SideTablesMap.init();
// _objc_associations_init();
// }
arr_init();
// 部分代码省略
for (uint32_t i = 0; i < hCount; i++) {
auto hi = hList[i];
auto mh = hi->mhdr();
if (mh->filetype != MH_EXECUTE) continue;
unsigned long size;
if (getsectiondata(hi->mhdr(), "__DATA", "__objc_fork_ok", &size)) {
DisableInitializeForkSafety = true;
if (PrintInitializing) {
_objc_inform("INITIALIZE: disabling +initialize fork "
"safety enforcement because the app has "
"a __DATA,__objc_fork_ok section");
}
}
break; // assume only one MH_EXECUTE image
}
#endif
}
//下面就来到了最核心的地方
// 以 header_info *hList[mhCount] 数组中收集到的 images 的 header_info 为参,直接进行 image 的读取
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
// 把开始时初始化的静态局部变量 firstTime 置为 NO
firstTime = NO;
// 一切设置完毕后调用 image 加载函数。
// Call image load funcs after everything is set up.
for (auto func : loadImageFuncs) {
for (uint32_t i = 0; i < mhCount; i++) {
func(mhdrs[i]);
}
}
sel_init
sel_init主要是初始化selector表,该表定义如下:
static objc::ExplicitInitDenseSet<const char *> namedSelectors;
namedSelectors是个全局变量,存储所有的方法名SEL,内部结构是哈希表DenseMap。
arr_init
arr_init主要做以下工作:
-
- 自动释放池的初始化,关于
AutoreleasePool的内容可以看我的另一篇文章:Autoreleasepool
- 自动释放池的初始化,关于
-
SideTablesMap初始化,也可理解为 SideTables 的初始化(为SideTables这个静态全局变量开辟空间)
-
AssociationsManager的初始化,即为全局使用的关联对象表开辟空间
_read_images
之后就进入到了_read_images,先看下传入的参数。
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
hList统计mhdrs中的每个mach_header对应的header_infohCount统计到的header_info数量totalClasses计算到的所有class的数量,该数量包含unoptimizedTotalClasses数量,可在addHeader函数中看到对这两个数的赋值(地址饮用)。unoptimizedTotalClasses不在共享缓存区找到的类的数量。 然后进入到函数体里边:
加载类到gdb_objc_realized_classes表中
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
header_info *hi;
uint32_t hIndex;
size_t count;
size_t i;
Class *resolvedFutureClasses = nil;
size_t resolvedFutureClassCount = 0;
// 静态局部变量,如果是第一次调用 _read_images 则 doneOnce 值为 NO
static bool doneOnce;
bool launchTime = NO;
// 测量 image 加载步骤的持续时间
// 对应 objc-env.h 中的 OPTION( PrintImageTimes, OBJC_PRINT_IMAGE_TIMES, "measure duration of image loading steps")
TimeLogger ts(PrintImageTimes);
runtimeLock.assertLocked();
//EACH_HEADER 是给下面的 for 循环使用的宏,遍历 hList 数组中的 header_info
#define EACH_HEADER \
hIndex = 0; \
hIndex < hCount && (hi = hList[hIndex]); \
hIndex++
// 第一次调用 _read_images 时,doneOnce 值为 NO,会进入 if 执行里面的代码
if (!doneOnce) {
doneOnce = YES;
launchTime = YES;
// 部分代码省略
// namedClasses
// Preoptimized classes don't go in this table.
// 4/3 is NXMapTable's load factor
// isPreoptimized 如果我们有一个有效的优化共享缓存(valid optimized shared cache),则返回 YES。
// 然后是不管三目运算符返回的是 unoptimizedTotalClasses 还是 totalClasses,它都会和后面的 4 / 3 相乘,
// 注意是 4 / 3
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
// gdb_objc_realized_classes 是一张全局的哈希表
// 实际上它存放的是不在 dyld shared cache 中的 class,无论该 class 是否 realized。
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
ts.log("IMAGE TIMES: first time tasks");
}
到这里基本是一些环境变量的初始化,以及在为
读取header_info内容做准备。
doneOnce保证在调用_read_images的时候只执行一次NXCreateMapTable为一个哈希表,通过类名来存储类对象(以及读取类对象)gdb_objc_realized_classes是一个全局的类表,只要 class 没有在共享缓存中,那么不管其实现或者未实现都会存在这个类表里面。
将所有的SEL都注册到namedSelectors表中,并且修复函数指针
// Fix up @selector references
// Note this has to be before anyone uses a method list, as relative method
// lists point to selRefs, and assume they are already fixed up (uniqued).
static size_t UnfixedSelectors;
{
mutex_locker_t lock(selLock);
// 遍历 header_info **hList 中的 header_info
for (EACH_HEADER) {
// 如果指定的 hi 不需要预优化则跳过
if (hi->hasPreoptimizedSelectors()) continue;
// 根据 mhdr()->filetype 判断 image 是否是 MH_BUNDLE 类型
bool isBundle = hi->isBundle();
// GETSECT(_getObjc2SelectorRefs, SEL, "__objc_selrefs");
// 获取 __objc_selrefs 区中的 SEL
SEL *sels = _getObjc2SelectorRefs(hi, &count);
// 记录数量
UnfixedSelectors += count;
// static objc::ExplicitInitDenseSet<const char *> namedSelectors;
// 是一个静态全局 set,用来存放 Selector(名字,Selector 本身就是字符串)
// 遍历把 sels 中的所有 selector 放进全局的 selector 集合中
for (i = 0; i < count; i++) {
// sel_cname 函数内部实现是返回:(const char *)(void *)sel; 即把 SEL 强转为 char 类型
const char *name = sel_cname(sels[i]);
// 注册 SEL,并返回其地址
SEL sel = sel_registerNameNoLock(name, isBundle);
// 如果 SEL 地址发生变化,则把它设置为相同
if (sels[i] != sel) {
sels[i] = sel;
}
}
}
}
_getObjc2SelectorRefs该函数是拿到Mach-o中的静态段__obj_selrefs- 后文中所有通过
_getObjc2开头的Mach-o静态段获取,都对应不同的section name,如下代码:
// function name content type section name
GETSECT(_getObjc2SelectorRefs, SEL, "__objc_selrefs");
GETSECT(_getObjc2MessageRefs, message_ref_t, "__objc_msgrefs");
GETSECT(_getObjc2ClassRefs, Class, "__objc_classrefs");
GETSECT(_getObjc2SuperRefs, Class, "__objc_superrefs");
GETSECT(_getObjc2ClassList, classref_t const, "__objc_classlist");
GETSECT(_getObjc2StubList, stub_class_t *const, "__objc_stublist");
GETSECT(_getObjc2NonlazyClassList, classref_t const, "__objc_nlclslist");
GETSECT(_getObjc2CategoryList, category_t * const, "__objc_catlist");
GETSECT(_getObjc2CategoryList2, category_t * const, "__objc_catlist2");
GETSECT(_getObjc2NonlazyCategoryList, category_t * const, "__objc_nlcatlist");
GETSECT(_getObjc2ProtocolList, protocol_t * const, "__objc_protolist");
GETSECT(_getObjc2ProtocolRefs, protocol_t *, "__objc_protorefs");
GETSECT(getLibobjcInitializers, UnsignedInitializer, "__objc_init_func");
这里的这段代码主要做了一件事情,也就是将所有的SEL都注册到namedSelectors表中,且当 SEL *sels = _getObjc2SelectorRefs(hi, &count); 中的 SEL 和通过 SEL sel = sel_registerNameNoLock(name, isBundle); 注册返回的 SEL 不同时,就把 sels 中的 SEL 修正为 sel_registerNameNoLock 中返回的地址,如下图所示。
这一过程也是前一篇文章中提到的
rebase的过程,也就是修复指向当前镜像内部的资源指针,导致这两个地址不同的原因为alsr偏移。
Discover classes
// Discover classes. Fix up unresolved future classes. Mark bundle classes.
// 发现 classes。修复 unresolved future classes。标记 bundle classes。
bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
for (EACH_HEADER) {
if (! mustReadClasses(hi, hasDyldRoots)) {
// Image is sufficiently optimized that we need not call readClass()
// Image 已充分优化,我们无需调用 readClass()
continue;
}
// GETSECT(_getObjc2ClassList, classref_t const, "__objc_classlist");
// 获取 __objc_classlist 区中的 classref_t
// 从编译后的类列表中取出所有类,获取到的是一个 classref_t 类型的指针
// classref_t is unremapped class_t* ➡️ classref_t 是未重映射的 class_t 指针
// typedef struct classref * classref_t; // classref_t 是 classref 结构体指针
classref_t const *classlist = _getObjc2ClassList(hi, &count);
bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->hasPreoptimizedClasses();
for (i = 0; i < count; i++) {
Class cls = (Class)classlist[i]; 人大在;s d
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
if (newCls != cls && newCls) {
// Class was moved but not deleted. Currently this occurs
// only when the new class resolved a future class.
// Non-lazily realize the class below.
// realloc 原型是 extern void *realloc(void *mem_address, unsigned int newsize);
// 先判断当前的指针是否有足够的连续空间,如果有,扩大 mem_address 指向的地址,并且将 mem_address 返回,
// 如果空间不够,先按照 newsize 指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,
// 而后释放原来 mem_address 所指内存区域(注意:原来指针是自动释放,不需要使用 free),
// 同时返回新分配的内存区域的首地址,即重新分配存储器块的地址。
resolvedFutureClasses = (Class *)
realloc(resolvedFutureClasses,
(resolvedFutureClassCount+1) * sizeof(Class));
resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
}
}
}
这一部分主要做了一件事:发现并读取class。
readClass
readClass主要是读取类,在未调用该方法前,cls只是一个地址,该方法执行后,cls存入表中,是一个类的名称
/***********************************************************************
* readClass
* Read a class and metaclass as written by a compiler.
读取由编译器编写的类和元类。
* Returns the new class pointer. This could be:
返回新的类指针。这可能是:
* - cls
* - nil (cls has a missing weak-linked superclass)
* - something else (space for this class was reserved by a future class)
*
* Note that all work performed by this function is preflighted by
* mustReadClasses(). Do not change this function without updating that one.
请注意,此函数执行的所有工作都由 mustReadClasses() 预检。
*
* Locking: runtimeLock acquired by map_images or objc_readClassPair
**********************************************************************/
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
// 类的名字
const char *mangledName = cls->nonlazyMangledName();
if (missingWeakSuperclass(cls)) {
// No superclass (probably weak-linked).
// Disavow any knowledge of this subclass.
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING class '%s' with "
"missing weak-linked superclass",
cls->nameForLogging());
}
addRemappedClass(cls, nil);
cls->setSuperclass(nil);
return nil;
}
// 如果 cls 是 swift 类,进行一些修正
cls->fixupBackwardDeployingStableSwift();
//判断class它是否存在于 future_named_class_map 中
Class replacing = nil;
if (mangledName != nullptr) {
if (Class newCls = popFutureNamedClass(mangledName)) {
// This name was previously allocated as a future class.
// Copy objc_class to future class's struct.
// Preserve future's rw data block.
if (newCls->isAnySwift()) {
_objc_fatal("Can't complete future class request for '%s' "
"because the real class is too big.",
cls->nameForLogging());
}
class_rw_t *rw = newCls->data();
const class_ro_t *old_ro = rw->ro();
memcpy(newCls, cls, sizeof(objc_class));
// Manually set address-discriminated ptrauthed fields
// so that newCls gets the correct signatures.
newCls->setSuperclass(cls->getSuperclass());
newCls->initIsa(cls->getIsa());
rw->set_ro((class_ro_t *)newCls->data());
newCls->setData(rw);
freeIfMutable((char *)old_ro->getName());
free((void *)old_ro);
addRemappedClass(cls, newCls);
replacing = cls;
cls = newCls;
}
}
// headerIsPreoptimized 是外部参数,只有该类禁用了预优化才会返回 true,所以到这里会走下面的 else
if (headerIsPreoptimized && !replacing) {
// class list built in shared cache
// fixme strict assert doesn't work because of duplicates
// ASSERT(cls == getClass(name));
ASSERT(mangledName == nullptr || getClassExceptSomeSwift(mangledName));
} else {
if (mangledName) { //some Swift generic classes can lazily generate their names
addNamedClass(cls, mangledName, replacing);
} else {
Class meta = cls->ISA();
const class_ro_t *metaRO = meta->bits.safe_ro();
ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass.");
ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class.");
}
addClassTableEntry(cls);
}
// for future reference: shared cache never contains MH_BUNDLEs
// 如果 headerIsBundle 为真,则设置下面的标识位 RO_FROM_BUNDLE
if (headerIsBundle) {
cls->data()->flags |= RO_FROM_BUNDLE;
cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
}
return cls;
}
这里边有一些判断,我们不知道我们添加的类会走哪些分支,我们可以添加如下代码然后加断点调试来看下到底会走哪些分支:
//FMUserInfo为我自己添加的类名
const char * userinfoName = "FMUserInfo";
auto fm_ro = (const class_ro_t *)cls ->data();
//判断是否是元类
auto fm_isMeta = fm_ro->flags & RO_META;
if (strcmp(mangledName,userinfoName) == 0 && !fm_isMeta) {
<#statements#>
}
其实整个readClass的过程,最终实际运行函数主要为以下两个,如下:
addNamedClass该函数是把name和cls添加到命名为gdb_objc_realized_classes的表中去,在addNamedClass函数中,调用NXMapInsert把cls 和 name插入到NXMapTable中。addClassTableEntry将类和元类加入到allocatedClasses表中
NXMapInsert
typedef struct _NXMapTable {
/* private data structure; may change */
const struct _NXMapTablePrototype * _Nonnull prototype;
unsigned count;
unsigned nbBucketsMinusOne;
void * _Nullable buckets;
} NXMapTable
void *NXMapInsert(NXMapTable *table, const void *key, const void *value) {
MapPair *pairs = (MapPair *)table->buckets;
unsigned index = bucketOf(table, key);
MapPair *pair = pairs + index;
if (key == NX_MAPNOTAKEY) {
_objc_inform("*** NXMapInsert: invalid key: -1\n");
return NULL;
}
unsigned numBuckets = table->nbBucketsMinusOne + 1;
if (pair->key == NX_MAPNOTAKEY) {
pair->key = key; pair->value = value;
table->count++;
if (table->count * 4 > numBuckets * 3) _NXMapRehash(table);
return NULL;
}
if (isEqual(table, pair->key, key)) {
const void *old = pair->value;
if (old != value) pair->value = value;/* avoid writing unless needed! */
return (void *)old;
} else if (table->count == numBuckets) {
/* no room: rehash and retry */
_NXMapRehash(table);
return NXMapInsert(table, key, value);
} else {
unsigned index2 = index;
while ((index2 = nextIndex(table, index2)) != index) {
pair = pairs + index2;
if (pair->key == NX_MAPNOTAKEY) {
pair->key = key; pair->value = value;
table->count++;
if (table->count * 4 > numBuckets * 3) _NXMapRehash(table);
return NULL;
}
if (isEqual(table, pair->key, key)) {
const void *old = pair->value;
if (old != value) pair->value = value;/* avoid writing unless needed! */
return (void *)old;
}
}
/* no room: can't happen! */
_objc_inform("**** NXMapInsert: bug\n");
return NULL;
}
}
整个NXMapInsert的操作就是一个普通的哈希表的插入操作
bucketOf(table, key)调用 table->prototype->hash 函数计算 key 在 table 中的哈希值numBuckets值为table->nbBucketsMinusOne + 1,也就是buckets 长度桶长度。- 如果
pair->key的值为-1,表示该位置还没有存入东西,则把key和value存在这里,如果当前 table 存储的数据已经超过了其容量的 3 / 4,则进行扩容并重新哈希化里面的数据。 if (isEqual(table, pair->key, key))如果pair的key和入参key相同,则表示key已经存在于table中(则更新value)if (table->count == numBuckets)如果刚好没有空间了,则扩容并重新哈希化旧数据后,再尝试插入key和value- 如果发生了哈希碰撞,则采用
开放寻址法来解决哈希冲突。
addClassTableEntry
addClassTableEntry 函数,将 cls 添加到全局的类表中。如果 addMeta 为 true,则也会把 cls 的元类添加到全局的类表中。
/***********************************************************************
* addClassTableEntry
* Add a class to the table of all classes. If addMeta is true,
* automatically adds the metaclass of the class as well.
将 cls 添加到全局的类表中。如果 addMeta 参数为 true,则也会把 cls 的元类添加到全局的类表中。
* Locking: runtimeLock must be held by the caller.
**********************************************************************/
static void
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.
// 在runtime_init 函数中,调用了 objc::allocatedClasses.init();对类的表进行了初始化。
//这里就是往类表中添加
auto &set = objc::allocatedClasses.get();
ASSERT(set.find(cls) == set.end());
// isKnownClass 函数,如果 runtime 知道该类,则返回 true,当以下情况时返回 true:
// 1. cls 位于 shared cache
// 2. cls 在加载 image 的 data segment 内
// 3. cls 已用 obj_allocateClassPair 分配
// 此操作的结果会缓存在类的 cls->data()->witness 中,
// 即我们的 class_rw_t 结构体的 witness 成员变量。
// struct class_rw_t {
// ...
// uint16_t witness;
// ...
// }
if (!isKnownClass(cls))
set.insert(cls);
if (addMeta)
// 把 cls 添加到类表中
addClassTableEntry(cls->ISA(), false);
}
总结:readClass的主要操作就是将Mach-o中的类读取到全局表中,但目前类中的信息仅有两个:地址+名称,类中的data数据,需要通过下文的realizeClassWithoutSwift来添加。
回到read_image函数中,接下来就是修复重映射classes
修复重映射一些没有被镜像文件加载进来的类
// Fix up remapped classes
// Class list and nonlazy class list remain unremapped.
// Class list 和 nonlazy class list 仍未映射。
// Class refs and super refs are remapped for message dispatching.
// Class refs 和 super refs 被重新映射为消息调度。
// 主要是修复重映射 classes,!noClassesRemapped() 在这里为 false,所以一般走不进来,
// 将未映射 class 和 super class 重映射,被 remap 的类都是非懒加载的类
if (!noClassesRemapped()) {
for (EACH_HEADER) {
Class *classrefs = _getObjc2ClassRefs(hi, &count);
// 遍历 classrefs 中的类引用,如果类引用已被重新分配或者是被忽略的弱链接类,
// 就将该类引用重新赋值为从重映射类表中取出新类
for (i = 0; i < count; i++) {
// 修复 class ref,以防所引用的类已 reallocated 或 is an ignored weak-linked class。
remapClassRef(&classrefs[i]);
}
// fixme why doesn't test future1 catch the absence of this?
classrefs = _getObjc2SuperRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[i]);
}
}
}
这里主要是将未映射的class和super class进行重映射,其中:
_getObjc2ClassRefs是获取Mach-o中的静态段__objc_classrefs,即类的引用_getObjc2SuperRefs是获取Mach-o中的静态段__objc_superrefs,即父类的引用
修复一些消息
for (EACH_HEADER) {
message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
if (count == 0) continue;
if (PrintVtables) {
_objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
"call sites in %s", count, hi->fname());
}
for (i = 0; i < count; i++) {
fixupMessageRef(refs+i);
}
}
修复一些消息的调用,这里边最主要的就是fixupMessageRef
当类里边有协议时,协议的处理
// Discover protocols. Fix up protocol refs.
for (EACH_HEADER) {
extern objc_class OBJC_CLASS_$_Protocol;
Class cls = (Class)&OBJC_CLASS_$_Protocol;
ASSERT(cls);
NXMapTable *protocol_map = protocols();
bool isPreoptimized = hi->hasPreoptimizedProtocols();
// Skip reading protocols if this is an image from the shared cache
// and we support roots
// Note, after launch we do need to walk the protocol as the protocol
// in the shared cache is marked with isCanonical() and that may not
// be true if some non-shared cache binary was chosen as the canonical
// definition
if (launchTime && isPreoptimized) {
if (PrintProtocols) {
_objc_inform("PROTOCOLS: Skipping reading protocols in image: %s",
hi->fname());
}
continue;
}
bool isBundle = hi->isBundle();
protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
for (i = 0; i < count; i++) {
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}
- 这里通过
NXMapTable *protocol_map = protocols();来创建protocol哈希表,表的名称为protocol_map; - 通过
_getObjc2ProtocolList获取到Mach-o中的静态段__objc_protolist协议列表,即从编译器中读取并初始化protocol - 循环遍历协议列表,通过
readProtocol方法将协议添加到protocol_map哈希表中
修复没有被加载到的协议
// Fix up @protocol references
// Preoptimized images may have the right
// answer already but we don't know for sure.
for (EACH_HEADER) {
// At launch time, we know preoptimized image refs are pointing at the
// shared cache definition of a protocol. We can skip the check on
// launch, but have to visit @protocol refs for shared cache images
// loaded later.
if (launchTime && hi->isPreoptimized())
continue;
protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
for (i = 0; i < count; i++) {
remapProtocolRef(&protolist[i]);
}
}
这里主要是通过_getObjc2ProtocolRefs获取到Mach-o的静态段__objc_protorefs。(
这里的__objc_protorefs与上文中的__objc_protolist并不是同一个东西),然后遍历需要修复的协议,通过remapProtocolRef比较当前协议和协议列表中的同一个内存地址的协议是否是相同的,如果不相同则替换
/***********************************************************************
* remapProtocolRef
* Fix up a protocol ref, in case the protocol referenced has been reallocated.
* Locking: runtimeLock must be read- or write-locked by the caller
**********************************************************************/
static size_t UnfixedProtocolReferences;
static void remapProtocolRef(protocol_t **protoref)
{
runtimeLock.assertLocked();
//获取协议列表中统一内存地址的协议
protocol_t *newproto = remapProtocol((protocol_ref_t)*protoref);
//如果当前协议与同一内存地址协议不同则替换
if (*protoref != newproto) {
*protoref = newproto;
UnfixedProtocolReferences++;
}
}
分类处理
下一篇中说这部分,待续。
非懒加载类的加载
懒加载类和非懒加载类的区别就是是否实现了+load方法
- 实现了
+load方法,就是非懒加载类 - 没实现,就是
懒加载类这是因为load会提前加载,load方法会在load_images中调用。
// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) {
classref_t const *classlist = hi->nlclslist(&count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
addClassTableEntry(cls);
if (cls->isSwiftStable()) {
if (cls->swiftMetadataInitializer()) {
_objc_fatal("Swift class %s with a metadata initializer "
"is not allowed to be non-lazy",
cls->nameForLogging());
}
// fixme also disallow relocatable classes
// We can't disallow all Swift classes because of
// classes like Swift.__EmptyArrayStorage
}
realizeClassWithoutSwift(cls, nil);
}
}
这里主要是实现非懒加载类的加载处理:
- 在
hi->nlclslist(&count)中通过_getObjc2NonlazyClassList获取Mach-o的静态段__objc_nlclslist非懒加载类表; - 通过
addClassTableEntry将非懒加载类插入类表,存储到内存,如果已经添加就不会再添加,需要确保整个结构都被添加; - 通过
realizeClassWithoutSwift实现当前的类,因为之前readClass读取到内存的仅仅只有地址+名称,类的data数据并没有加载出来。 具体的类如何实现,realizeClassWithoutSwift函数的调用,也放在下一篇中,待续。
没有被处理的类
// Realize newly-resolved future classes, in case CF manipulates them
if (resolvedFutureClasses) {
for (i = 0; i < resolvedFutureClassCount; i++) {
Class cls = resolvedFutureClasses[i];
if (cls->isSwiftStable()) {
_objc_fatal("Swift class is not allowed to be future");
}
realizeClassWithoutSwift(cls, nil);
// 将此类及其所有子类标记为需要原始 isa 指针
cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
}
free(resolvedFutureClasses);
}
read_images 总结
- 1: 加载所有类到类的
gdb_objc_realized_classes表中。 - 2: 对所有类做重映射。
- 3: 将所有SEL都注册到
namedSelectors表中。 - 4: 修复函数指针遗留。
- 5: 将所有Protocol都添加到
protocol_map表中。 - 6: 对所有Protocol做重映射。
- 7: 初始化所有⾮懒加载的类,
进⾏rw、ro等操作。 - 8:遍历已标记的懒加载的类,并做初始化操作。
- 9:处理所有
Category,包括Class和Meta Class。 - 10:初始化所有未初始化的类。
load_images
首先看下load_images源码:
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
//加载所有分类
loadAllCategories();
}
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// 找到load方法
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
//回调load方法
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
通过源码来看load_images函数整体上做了三件事:
loadAllCategories加载所有分类prepare_load_methods找到load方法call_load_methods调用load方法
prepare_load_methods
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
// GETSECT(_getObjc2NonlazyClassList, classref_t const, "__objc_nlclslist");
// 获取所有 __objc_nlclslist 区的数据(所有非懒加载类)
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
// 遍历这些非懒加载类,并将其 +load 函数添加到 loadable_classes 数组中,优先添加其父类的 +load 方法,
// 用于下面 call_load_methods 函数调用
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
// GETSECT(_getObjc2NonlazyCategoryList, category_t * const, "__objc_nlcatlist");
// 获取所有 __objc_nlcatlist 区的数据(所有非懒加载分类)
category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
// 遍历这些分类
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
// 如果没有找到分类所属的类就跳出当前循环,处理数组中的下一个分类
if (!cls) continue; // category for ignored weak-linked class
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
// 如果分类所属的类没有实现就先去实现
realizeClassWithoutSwift(cls, nil);
ASSERT(cls->ISA()->isRealized());
// 需要调用 +load 的 categories 列表
// static struct loadable_category *loadable_categories = nil;
// 遍历这些分类,并将其 +load 方法添加到 loadable_categories 数组中保存
add_category_to_loadable_list(cat);
}
}
- 这里的
schedule_class_load会进行一个递归调用,在找load方法的时候,会优先去找父类的load方法,然后把找到的方法添加到loadable_classes表里边去 - add_class_to_loadable_list会去找分类的
load方法,在找到之后同样添加到loadable_classes表里边去
// Recursively schedule +load for cls and any un-+load-ed superclasses.
// cls must already be connected.
static void schedule_class_load(Class cls)
{
// 如果 cls 不存在则 return(下面有一个针对 superclass 的递归调用)
if (!cls) return;
ASSERT(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
// 这是一个递归调用
// 优先处理 superclass 的 +load 函数
schedule_class_load(cls->getSuperclass());
// 将 cls 的 +load 函数添加到全局的 loadable_class 数组 loadable_classes 中,
// loadable_class 结构体是用来保存类的 +load 函数的一个数据结构,其中 cls 是该类,method 则是 +load 函数的 IMP,
// 这里也能看出 +load 函数是不走 OC 的消息转发机制的,它是直接通过 +load 函数的地址调用的!
add_class_to_loadable_list(cls);
// 将 RW_LOADED 设置到类的 Flags 中
cls->setInfo(RW_LOADED);
}
call_load_methods
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
// 创建自动释放池
void *pool = objc_autoreleasePoolPush();
do {
// 调用类中的 +load 函数
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
// 调用 loadable_classes 中的的类的 +load 函数,并且把 loadable_classes_used 置为 0
call_class_loads();
}
// 2. Call category +loads ONCE
// 调用 分类中的 +load 函数, 只调用一次 call_category_loads,因为上面的 call_class_loads 函数内部,
// 已经把 loadable_classes_used 置为 0,所以除非有新的分类需要 +load,即 call_category_loads 返回 true,
// 否则循环就结束了。
more_categories = call_category_loads();
// 如果 loadable_classes_used 大于 0,或者有更多分类需要调用 +load,则循环继续。(一般 loadable_classes_used 到这里基本就是 0 了)
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
// 自动释放池进行 pop
objc_autoreleasePoolPop(pool);
loading = NO;
}
load方法总结
- 当父类和子类都实现load函数时,父类的load方法执行顺序要优先于子类
- 当一个类未实现load方法时,不会调用父类load方法
- 类中的load方法执行顺序要优先于分类(Category)
- load方法使用了锁,所以是线程安全的。
- 当有多个类别(Category)都实现了load方法,这几个load方法都会执行,但执行顺序不确定(其执行 顺序与类别在
Compile Sources中出现的顺序一致) - 当然当有多个不同的类的时候,每个类load 执行顺序与其在
Compile Sources出现的顺序一致