前文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_info
-
hCount
统计到的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
呈现的次序一致