先回顾下这张图,回顾下整体流程。现在分析下在此流程中objc4源码(818.2)的处理逻辑。
我们在上图可以看出,dyld在main
函数之前(pre-main)会间接调用到objc
的_objc_init
,其中使用_dyld_objc_notify_register
注册了3个方法,但在这之前还做了一些初始化的操作。
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/
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();
//绑定线程析构函数
tls_init();
//静态构造函数
static_init();
//runtime准备,创建2张表
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
是读取环境变量(environment variables
)的一些配置信息,environment variables
在Product
->Scheme
->Edit Scheme
->Run
->Argments
->Environment Variables
中配置。设置相关信息可以打印一些信息。可以设置OBJC_HELP
,启动程序后会打印所有可以设置的信息已经含义解释。
输出:
objc[2763]: OBJC_HELP: describe available environment variables//描述可用的环境变量
objc[2763]: OBJC_HELP is set
objc[2763]: OBJC_PRINT_OPTIONS: list which options are set//列出设置的选项
objc[2763]: OBJC_PRINT_OPTIONS is set
objc[2763]: OBJC_PRINT_IMAGES: log image and library names as they are loaded//在加载时输出image和library名称
objc[2763]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[2763]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods//打印Class及Category的+(void)load 方法的调用信息
objc[2763]: OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods//打印Class的+(void)initialize的调用信息
objc[2763]: OBJC_PRINT_RESOLVED_METHODS: log methods created by +resolveClassMethod: and +resolveInstanceMethod://打印通过+resolveClassMethod:和+resolveInstanceMethod:生成的类方法
objc[2763]: OBJC_PRINT_CLASS_SETUP: log progress of class and category setup//打印Class及Category的设置过程
objc[2763]: OBJC_PRINT_PROTOCOL_SETUP: log progress of protocol setup//打印Protocol的设置过程
objc[2763]: OBJC_PRINT_IVAR_SETUP: log processing of non-fragile ivars//打印lar的设置过程
objc[2763]: OBJC_PRINT_VTABLE_SETUP: log processing of class vtables//打印vtable的设置过程
objc[2763]: OBJC_PRINT_VTABLE_IMAGES: print vtable images showing overridden methods//打印vtable被覆盖的方法
objc[2763]: OBJC_PRINT_CACHE_SETUP: log processing of method caches//打印方法缓存的设置过程
objc[2763]: OBJC_PRINT_FUTURE_CLASSES: log use of future classes for toll-free bridging//打印从CFType无缝转换到NSObject将要使用的类(如CFArrayRef到NSArray *)
objc[2763]: OBJC_PRINT_PREOPTIMIZATION: log preoptimization courtesy of dyld shared cache//打印dyld共享缓存优化前的问候语
objc[2763]: OBJC_PRINT_CXX_CTORS: log calls to C++ ctors and dtors for instance variables//打印类实例中的C++对象的构造与析构调用
objc[2763]: OBJC_PRINT_EXCEPTIONS: log exception handling//打印异常处理
objc[2763]: OBJC_PRINT_EXCEPTION_THROW: log backtrace of every objc_exception_throw()//打印所有异常抛出时的回溯
objc[2763]: OBJC_PRINT_ALT_HANDLERS: log processing of exception alt handlers//打印alt操作异常处理
objc[2763]: OBJC_PRINT_REPLACED_METHODS: log methods replaced by category implementations//打印被Category替换的方法
objc[2763]: OBJC_PRINT_DEPRECATION_WARNINGS: warn about calls to deprecated runtime functions//打印所有过时的方法调用
objc[2763]: OBJC_PRINT_POOL_HIGHWATER: log high-water marks for autorelease pools//打印autoreleasepool高水位警告
objc[2763]: OBJC_PRINT_CUSTOM_CORE: log classes with custom core methods//打印具有自定义核心方法的类
objc[2763]: OBJC_PRINT_CUSTOM_RR: log classes with custom retain/release methods//打印含有未优化的自定义retain/release方法的类
objc[2763]: OBJC_PRINT_CUSTOM_AWZ: log classes with custom allocWithZone methods//打印含有未优化的自定义allocWithzone方法的类
objc[2763]: OBJC_PRINT_RAW_ISA: log classes that require raw pointer isa fields//打印需要访问原始isa指针的类
objc[2763]: OBJC_DEBUG_UNLOAD: warn about poorly-behaving bundles when unloaded//卸载有不良行为的Bundle时打印警告
objc[2763]: OBJC_DEBUG_FRAGILE_SUPERCLASSES: warn about subclasses that may have been broken by subsequent changes to superclasses//当子类可能被对父类的修改破坏时打印警告
objc[2763]: OBJC_DEBUG_NIL_SYNC: warn about @synchronized(nil), which does no synchronization//警告@synchronized(nil)调用,这种情况不会加锁
objc[2763]: OBJC_DEBUG_NONFRAGILE_IVARS: capriciously rearrange non-fragile ivars//打印突发地重新布置non-fragileivars的行为
objc[2763]: OBJC_DEBUG_ALT_HANDLERS: record more info about bad alt handler use//记录更多的alt操作错误信息
objc[2763]: OBJC_DEBUG_MISSING_POOLS: warn about autorelease with no pool in place, which may be a leak//警告没有pool的情況下使用autorelease,可能内存泄漏
objc[2763]: OBJC_DEBUG_POOL_ALLOCATION: halt when autorelease pools are popped out of order, and allow heap debuggers to track autorelease pools//当自动释放池无序弹出时停止,并允许堆调试器跟踪自动释放池
objc[2763]: OBJC_DEBUG_DUPLICATE_CLASSES: halt when multiple classes with the same name are present//当出现类重名时停机
objc[2763]: OBJC_DEBUG_DONT_CRASH: halt the process by exiting instead of crashing//通过退出而不是崩溃来停止进程
objc[2763]: OBJC_DEBUG_POOL_DEPTH: log fault when at least a set number of autorelease pages has been allocated//分配了至少设定数量的自动发布页时打印错误
objc[2763]: OBJC_DEBUG_SCRIBBLE_CACHES: scribble the IMPs in freed method caches//在释放的方法缓存中将IMP改乱
objc[2763]: OBJC_DEBUG_SCAN_WEAK_TABLES: scan the weak references table continuously in the background - set OBJC_DEBUG_SCAN_WEAK_TABLES_INTERVAL_NANOSECONDS to set scanning interval (default 1000000)
objc[2763]: OBJC_DISABLE_VTABLES: disable vtable dispatch//关闭vtable分发
objc[2763]: OBJC_DISABLE_PREOPTIMIZATION: disable preoptimization courtesy of dyld shared cache//关闭avld共享缓存优化前的问候语
objc[2763]: OBJC_DISABLE_TAGGED_POINTERS: disable tagged pointer optimization of NSNumber et al.//关闭NSNumber等的tagged pointer优化
objc[2763]: OBJC_DISABLE_TAG_OBFUSCATION: disable obfuscation of tagged pointers
objc[2763]: OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields//关闭non-pointer isa字段的访问
objc[2763]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY: disable safety checks for +initialize after fork
objc[2763]: OBJC_DISABLE_FAULTS: disable os faults
objc[2763]: OBJC_DISABLE_PREOPTIMIZED_CACHES: disable preoptimized caches
objc[2763]: OBJC_DISABLE_AUTORELEASE_COALESCING: disable coalescing of autorelease pool pointers
objc[2763]: OBJC_DISABLE_AUTORELEASE_COALESCING_LRU: disable coalescing of autorelease pool pointers using look back N strategy
除此之外,environ_init
里面还对其他工程配置进行读取,比如NSZombiesEnabled
(僵尸对象检测)。
接下来是 tls_init
,tls
是Thread Local Store
的缩写,线程局部存储主要用于在多线程中,存储和维护一些线程相关的数据,存储的数据会被关联到当前线程中去,并不需要锁来维护。
/***********************************************************************
* tls_init
* 关联线程析构函数
* 当线程销毁,调用以上方法
**********************************************************************/
void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
_objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}
这里其实是给线程添加了析构函数,线程销毁,会调用_objc_pthread_destroyspecific
方法。
接下来简单介绍下TLS:我们知道在一个进程中,所有线程是共享同一个地址空间的。所以,如果一个变量是全局的或者是静态的,那么所有线程访问的是同一份,如果某一个线程对其进行了修改,也就会影响到其他所有的线程。不过我们可能并不希望这样,所以更多的推荐用基于堆栈的自动变量或函数参数来访问数据,因为基于堆栈的变量总是和特定的线程相联系的。
不过如果某些时候,我们就是需要依赖全局变量或者静态变量,那有没有办法保证在多线程程序中能访问而不互相影响呢?答案是有的。操作系统帮我们提供了这个功能——TLS线程本地存储。TLS的作用是能将数据和执行的特定的线程联系起来。
实现TLS有两种方法:静态TLS和动态TLS。
更多内容可以参考:博客、博客。
接下来是static_init
:找到objc
库中的所有初始化方法,遍历调用,注意:objc
库中的静态构造函数早于load
方法,load
方法早于我们自己的静态构造函数。
/***********************************************************************
* static_init
* Run C++ static constructor functions.
* libc calls _objc_init() before dyld would call our static constructors,
* so we have to do it ourselves.
**********************************************************************/
static void static_init()
{
size_t count;
//获取objc库里面所有的静态构造函数
auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
for (size_t i = 0; i < count; i++) {
inits[i]();
}
auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
//遍历调用他们
for (size_t i = 0; i < count; i++) {
UnsignedInitializer init(offsets[i]);
init();
}
}
接下来是runtime_init
:
void runtime_init(void)
{
//分类加载表
objc::unattachedCategories.init(32);
//类的加载表
objc::allocatedClasses.init();
}
这里面其实是初始化两张表,以备后边加载类使用,这里可以留意一下这两张表unattachedCategories
和allocatedClasses
,后面会再次提及到。
其他的初始化,由于不影响整个流程,所以在这一小节一带而过。
exception_init
:初始化异常捕捉相关
cache_t::init
:初始化缓存相关
_imp_implementationWithBlock_init
: MacOS中,让dyld
去加载libobjc-trampolines.dylib这个库。
上一篇文章,我们追踪到了load_images
的调用时机,这次我们直接来到他调用的地方,先分析其传入的参数。
三个参数:
参数 | 含义 |
---|---|
count | images的数量 |
paths[] | 库的路径(数组) |
mhdrs[] | 库对应的mach-header 信息(数组) |
再看map_image
的源码:
/***********************************************************************
* map_images
* Process the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
*
* Locking: write-locks runtimeLock
**********************************************************************/
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_nolock
函数。下面看一下map_images_nolock
:
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
// 是否是第一次加载
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.
// 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环境,打印镜像数量
// 如:objc[10503]: IMAGES: processing 296 newly-mapped images...
if (PrintImages) {
_objc_inform("IMAGES: processing %u newly-mapped images...\n", mhCount);
}
// Find all images with Objective-C metadata.
// 计算 class 的数量。根据总数调整各种表格的大小。
hCount = 0;
// 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;
}
//如果是可执行文件(我们的代码生成的)MH_EXECUTE就是我们主工程的代码
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;
// 获取 __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
}
//把hi存储起来
hList[hCount++] = hi;
if (PrintImages) {
// 打印 image 信息
_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)":"");
}
}
}
// Perform one-time runtime initialization that must be deferred until
// the executable itself is found. This needs to be done before
// further initialization.
// 执行 one-time runtime initialization,必须推迟到找到可执行文件本身。
// 这需要在进一步初始化之前完成。
// (The executable may not be present in this infoList if the
// executable does not contain Objective-C code but Objective-C
// is dynamically loaded later.
// 如果可执行文件不包含 Objective-C 代码但稍后动态加载 Objective-C,则该可执行文件可能不会出现在此 infoList 中。
if (firstTime) {
// 初始化函数注册表
sel_init(selrefCount);
// 这里的 arr_init 函数超重要,可看到它内部做了三件事
// 1.自动释放池 AutoreleasePoolPage的初始化
// 2.SideTablesMap初始化
// 3.全局关联对象表的初始化
arr_init();
/*
void arr_init(void)
{
AutoreleasePoolPage::init();
SideTablesMap.init();
_objc_associations_init();
if (DebugScanWeakTables)
startWeakTableScan();
}
*/
#if SUPPORT_GC_COMPAT
// Reject any GC images linked to the main executable.
// We already rejected the app itself above.
// Images loaded after launch will be rejected by dyld.
for (uint32_t i = 0; i < hCount; i++) {
auto hi = hList[i];
auto mh = hi->mhdr();
if (mh->filetype != MH_EXECUTE && shouldRejectGCImage(mh)) {
_objc_fatal_with_reason
(OBJC_EXIT_REASON_GC_NOT_SUPPORTED,
OS_REASON_FLAG_CONSISTENT_FAILURE,
"%s requires Objective-C garbage collection "
"which is no longer supported.", hi->fname());
}
}
#endif
// 这一段是在较低版本下 DYLD_MACOSX_VERSION_10_13 之前的版本中禁用 +initialize fork safety,大致看看即可
#if TARGET_OS_OSX
// Disable +initialize fork safety if the app is too old (< 10.13).
// Disable +initialize fork safety if the app has a
// __DATA,__objc_fork_ok section.
// if (!dyld_program_sdk_at_least(dyld_platform_version_macOS_10_13)) {
// DisableInitializeForkSafety = true;
// if (PrintInitializing) {
// _objc_inform("INITIALIZE: disabling +initialize fork "
// "safety enforcement because the app is "
// "too old.)");
// }
// }
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;
// _read_images 看完再看下面的 loadImageFuncs 函数
// Call image load funcs after everything is set up.
// 一切设置完毕后调用 image 加载函数。
for (auto func : loadImageFuncs) {
for (uint32_t i = 0; i < mhCount; i++) {
func(mhdrs[i]);
}
}
}
其中最重要的就是_read_images
函数的调用,map_images_nolock
上半部分就是对const struct mach_header * const mhdrs[]
参数的处理,把数组中的mach_header
转换为header_info
并存在header_info *hList[mhCount]
数组中,并统计totalClasses
和unoptimizedTotalClasses
的数量,然后调用_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses)
函数。
下面先看_read_images
的源码,发现非常长,下面根据顺序给出几段主要的代码:
/***********************************************************************
* _read_images
* Perform initial processing of the headers in the linked
* list beginning with headerList.
*
* Called by: map_images_nolock
*
* Locking: runtimeLock acquired by map_images
*
* 对以 headerList 开头的链接列表中的标头执行初始处理。
* 呼叫者: map_images_nolock
* 锁定:运行时锁定由map_images获取
**********************************************************************/
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 加载步骤的持续时间
TimeLogger ts(PrintImageTimes);
// 加锁
runtimeLock.assertLocked();
这里是初始化一些局部变量,注意OBJC_PRINT_IMAGE_TIMES
的设置可以使之后的ts.log()
生效。我们这分段也是通过ts.log()
来进行分段的。
// 1⃣️
// 第一次调用 _read_images 时,doneOnce 值为 NO,会进入 if 执行里面的代码
if (!doneOnce) {
// 把静态局部变量 doneOnce 置为 YES,之后调用 _read_images 都不会再进来
// 第一次调用 _read_images 的时候,class、protocol、selector、category 都没有,
// 需要创建容器来保存这些东西,此 if 内部,最后是创建一张存 class 的表。
doneOnce = YES;
launchTime = YES;
#if SUPPORT_NONPOINTER_ISA
// Disable non-pointer isa under some conditions.
# if SUPPORT_INDEXED_ISA
// Disable nonpointer isa if any image contains old Swift code
for (EACH_HEADER) {
if (hi->info()->containsSwift() &&
hi->info()->swiftUnstableVersion() < objc_image_info::SwiftVersion3)
{
DisableNonpointerIsa = true;
if (PrintRawIsa) {
_objc_inform("RAW ISA: disabling non-pointer isa because "
"the app or a framework contains Swift code "
"older than Swift 3.0");
}
break;
}
}
# endif
# if TARGET_OS_OSX
// Disable non-pointer isa if the app is too old
// (linked before OS X 10.11)
// Note: we must check for macOS, because Catalyst and Almond apps
// return false for a Mac SDK check! rdar://78225780
// if (dyld_get_active_platform() == PLATFORM_MACOS && !dyld_program_sdk_at_least(dyld_platform_version_macOS_10_11)) {
// DisableNonpointerIsa = true;
// if (PrintRawIsa) {
// _objc_inform("RAW ISA: disabling non-pointer isa because "
// "the app is too old.");
// }
// }
// Disable non-pointer isa if the app has a __DATA,__objc_rawisa section
// New apps that load old extensions may need this.
/*
#define EACH_HEADER \
hIndex = 0; \
hIndex < hCount && (hi = hList[hIndex]); \
hIndex++
*/
for (EACH_HEADER) {
if (hi->mhdr()->filetype != MH_EXECUTE) continue;
unsigned long size;
if (getsectiondata(hi->mhdr(), "__DATA", "__objc_rawisa", &size)) {
DisableNonpointerIsa = true;
if (PrintRawIsa) {
_objc_inform("RAW ISA: disabling non-pointer isa because "
"the app has a __DATA,__objc_rawisa section");
}
}
break; // assume only one MH_EXECUTE image
}
# endif
#endif
// 禁用 NSNumber 等的 Tagged Pointers 优化时
if (DisableTaggedPointers) {
// 内部直接把 Tagged Pointers 用到的 mask 全部置为 0
disableTaggedPointers();
/*
static void
disableTaggedPointers()
{
objc_debug_taggedpointer_mask = 0;
objc_debug_taggedpointer_slot_shift = 0;
objc_debug_taggedpointer_slot_mask = 0;
objc_debug_taggedpointer_payload_lshift = 0;
objc_debug_taggedpointer_payload_rshift = 0;
objc_debug_taggedpointer_ext_mask = 0;
objc_debug_taggedpointer_ext_slot_shift = 0;
objc_debug_taggedpointer_ext_slot_mask = 0;
objc_debug_taggedpointer_ext_payload_lshift = 0;
objc_debug_taggedpointer_ext_payload_rshift = 0;
}
*/
}
// 可开启 OBJC_DISABLE_TAG_OBFUSCATION,禁用 Tagged Pointer 的混淆。
// 随机初始化 objc_debug_taggedpointer_obfuscator。
// tagged pointer obfuscator 旨在使攻击者在存在缓冲区溢出或其他对某些内存的写控制的情况下更难将特定对象构造为标记指针。
// 在设置或检索有效载荷值(payload values)时, obfuscator 与 tagged pointers 进行异或。
// 它们在第一次使用时充满了随机性。
initializeTaggedPointerObfuscator();
if (PrintConnecting) {
_objc_inform("CLASS: found %d classes during launch", totalClasses);
}
// 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 是一张全局的哈希表,虽然名字中有 realized,但是它的名字其实是一个误称,
// 实际上它存放的是不在 dyld shared cache 中的 class,无论该 class 是否 realized。
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
ts.log("IMAGE TIMES: first time tasks");
}
注意这部分代码,通过静态变量doneOnce
控制,只会执行一次。其中创建的gdb_objc_realized_classes
这个表,后期会在addNamedClass
静态方法内部对表进行赋值。这部分主要做了两件事情:
OBJC_DISABLE_TAGGED_POINTERS
)判断是否禁用 Tagged Pointer
,禁用 Tagged Pointer
时所涉及到的 mask
都被设置为 0,然后根据环境变量(OBJC_DISABLE_TAG_OBFUSCATION
)以及是否是低版本系统来判断是否禁用 Tagged Pointer
的混淆器(obfuscation),禁用混淆器时 objc_debug_taggedpointer_obfuscator
的值 被设置为 0,否则为其设置一个随机值。NXCreateMapTable
根据类的数量(* 4/3,根据当前类的数量做动态扩容)创建一张哈希表(是 NXMapTable
结构体实例,NXMapTable
结构体是被作为哈希表来使用的,可通过类名(const char *
)来获取 Class 对象)并赋值给 gdb_objc_realized_classes
这个全局的哈希表,用来通过类名来存放类对象(以及读取类对象),即这个 gdb_objc_realized_classes
便是一个全局的类表,只要 class
没有在共享缓存中,那么不管其实现或者未实现都会存在这个类表里面。 // 2⃣️
// 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).
// Fix up @selector references
// 注册并修正 selector references
//(其实就是把 image 的 __objc_selrefs 区中的 selector 放进全局的 selector 集合中,
static size_t UnfixedSelectors;
{
// 加锁 selLock
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();
// 获取 __objc_selrefs 区中的 SEL
SEL *sels = _getObjc2SelectorRefs(hi, &count);
// 记录数量
UnfixedSelectors += count;
// static objc::ExplicitInitDenseSet 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_registerNameNoLock(name, isBundle);
//进行修正(fix up)如果地址发生变化,使用函数表中的地址
if (sels[i] != sel) {
sels[i] = sel;
}
}
}
}
// 这里打印注册并修正 selector references 用的时间
ts.log("IMAGE TIMES: fix up selector references");
把方法selector
从__objc_selrefs section
加载到map_images_nolock
中初始化的函数表中,并且进行修正。也就是当 SEL *sels = _getObjc2SelectorRefs(hi, &count);
中的 SEL
和通过 SEL sel = sel_registerNameNoLock(name, isBundle);
注册返回的 SEL
不同时,就把 sels
中的 SEL
修正为 sel_registerNameNoLock
中返回的地址。
// 3⃣️
// 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;
}
// 获取 __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];
// 重点 ⚠️⚠️⚠️⚠️ 在这里:readClass。
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;
}
}
}
// 这里打印发现 classes 用的时间
ts.log("IMAGE TIMES: discover classes");
// 4⃣️
// Fix up remapped classes
// Class list and nonlazy class list remain unremapped.
// Class refs and super refs are remapped for message dispatching.
// 主要是修复重映射 classes,!noClassesRemapped() 在这里为 false,所以一般走不进来,
// 将未映射 class 和 super class 重映射,被 remap 的类都是非懒加载的类
if (!noClassesRemapped()) {
for (EACH_HEADER) {
Class *classrefs = _getObjc2ClassRefs(hi, &count);
for (i = 0; i < count; i++) {
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]);
}
}
}
// 这里打印修复重映射 classes 用的时间
ts.log("IMAGE TIMES: remap classes");
// 5⃣️
// Discover protocols. Fix up protocol refs.
// 发现 protocols,修正 protocol refs。
for (EACH_HEADER) {
extern objc_class OBJC_CLASS_$_Protocol;
Class cls = (Class)&OBJC_CLASS_$_Protocol;
ASSERT(cls);
// 创建一个长度是 16 的 NXMapTable
NXMapTable *protocol_map = protocols();
bool isPreoptimized = hi->hasPreoptimizedProtocols();
// Skip reading protocols if this is an image from the shared cache
// and we support roots
// 如果这是来自 shared cache 的 image 并且我们 support roots,则跳过 reading protocols
// 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
// 启动后,我们确实需要遍历协议,因为 shared cache 中的协议用 isCanonical() 标记,如果选择某些非共享缓存二进制文件作为规范定义,则可能不是这样
if (launchTime && isPreoptimized) {
if (PrintProtocols) {
_objc_inform("PROTOCOLS: Skipping reading protocols in image: %s",
hi->fname());
}
continue;
}
bool isBundle = hi->isBundle();
// 获取 hi 的 __objc_protolist 区下的 protocol_t
protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
for (i = 0; i < count; i++) {
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}
// 这里打印发现 protocols 用的时间
ts.log("IMAGE TIMES: discover protocols");
/***********************************************************************
* protocols
* Returns the protocol name => protocol map for protocols.
* Locking: runtimeLock must read- or write-locked by the caller
**********************************************************************/
static NXMapTable *protocols(void)
{
static NXMapTable *protocol_map = nil;
runtimeLock.assertLocked();
INIT_ONCE_PTR(protocol_map,
NXCreateMapTable(NXStrValueMapPrototype, 16),
NXFreeMapTable(v) );
return protocol_map;
}
// 6⃣️
// Fix up @protocol references
// Preoptimized images may have the right
// answer already but we don't know for sure.
// Preoptimized images 可能已经有了正确的答案,但我们不确定。
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.
// 在启动时,我们知道 preoptimized image refs 指向协议的 shared cache 定义。
// 我们可以跳过启动时的检查,但必须访问 @protocol refs 以获取稍后加载的 shared cache images。
if (launchTime && hi->isPreoptimized())
continue;
// 获取 hi 的 __objc_protorefs 区的 protocol_t
protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
for (i = 0; i < count; i++) {
// 修复 protocol ref,以防 protocol referenced 已重新分配。
remapProtocolRef(&protolist[i]);
}
}
// 这里打印 @protocol references 用的时间
ts.log("IMAGE TIMES: fix up @protocol references");
// 7⃣️
// 下面把 category 的数据追加到原类中去!超重要....
// Discover categories.发现类别。
// Only do this after the initial category
// attachment has been done.
// 仅在完成 initial category attachment 后才执行此操作。
// For categories present at startup,
// discovery is deferred until the first load_images call after
// the call to _dyld_objc_notify_register completes. rdar://problem/53119145
// 对于启动时出现的 categories,discovery 被推迟到 _dyld_objc_notify_register 调用完成后的第一个 load_images 调用。
// 这里 if 里面的 category 数据加载是不会执行的。
// didInitialAttachCategories 是一个静态全局变量,默认是 false,
// static bool didInitialAttachCategories = false;
if (didInitialAttachCategories) {
for (EACH_HEADER) {
load_categories_nolock(hi);
}
}
// 这里打印 Discover categories. 用的时间
ts.log("IMAGE TIMES: discover categories");
注意:didInitialAttachCategories
只会在load_images
中设置为true
,也就是说只有执行过load_images
,这里才会遍历load_categories_nolock
。
// 8⃣️
// Realize non-lazy classes (for +load methods and static instances)
// 实现非懒加载类()
for (EACH_HEADER) {
// 获取 hi 的 __objc_nlclslist 区中的非懒加载类(即实现了 +load 函数的类)
classref_t const *classlist = hi->nlclslist(&count);
for (i = 0; i < count; i++) {
// 重映射类, 获取正确的类指针
Class cls = remapClass(classlist[i]);
if (!cls) continue;
// static void addClassTableEntry(Class cls, bool addMeta = true) { ... }
// 将一个类添加到用来存储所有类的全局的 set 中(auto &set = objc::allocatedClasses.get();)。
// 如果 addMeta 为 true(默认为 true),也自动添加类的元类到这个 set 中。
// 这个类可以通过 shared cache 或 data segments 成为已知类,但不允许已经在 dynamic table 中。
// allocatedClasses 是 objc 命名空间中的一个静态变量。
// A table of all classes (and metaclasses) which have been allocated with objc_allocateClassPair.
// 已使用 objc_allocateClassPair 分配空间的存储所有 classes(和 metaclasses)的 Set。
// namespace objc {
// static ExplicitInitDenseSet allocatedClasses;
// }
// 先把 cls 放入 allocatedClasses 中,然后递归把 metaclass 放入 allocatedClasses 中
addClassTableEntry(cls);
// 判断 cls 是否是来自稳定的 Swift ABI 的 Swift 类
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
// 也禁止 relocatable classes
// 我们不能因为像 Swift.__EmptyArrayStorage 这样的类而禁止所有 Swift 类
}
// 实现 Swift 之外的 classes
// 对类 cls 执行首次初始化,包括分配其读写数据。不执行任何 Swift 端初始化。返回类的真实类结构。
// 大概是设置 ro rw 和一些标识位的过程,也包括递归实现父类(supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);)
// 和元类(metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);),
// 然后更新 cls 的父类和元类(cls->superclass = supercls; cls->initClassIsa(metacls);),
// 将 cls 连接到其父类的子类列表(addSubclass(supercls, cls);)(操作 class_rw_t 的 Class firstSubclass; 和 Class nextSiblingClass; 两个成员变量),
// 修正 cls 的方法列表、协议列表和属性列表,
// 以及最后的附加任何未完成的 categories(主要包含 method list、protocol list、property list)
//(objc::unattachedCategories.attachToClass)。
realizeClassWithoutSwift(cls, nil);
}
}
// 这里打印 Realize non-lazy classes 用的时间
ts.log("IMAGE TIMES: realize non-lazy classes");
+load
函数,在使用的第一次才会加载,当我们给这个类的发送消息时,如果是第一次,在消息查找的过程中就会判断这个类是否加载,没有加载就会加载这个类。懒加载类在首次调用方法的时候,才会去调用 realizeClassWithoutSwift
函数去进行加载。+load
函数,类的加载就会提前。 // 9⃣️
// Realize newly-resolved future classes, in case CF manipulates them
// 实现 newly-resolved future classes,以防 CF 操作它们
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);
}
ts.log("IMAGE TIMES: realize future classes");
// 如果开启了 OBJC_DEBUG_NONFRAGILE_IVARS 这个环境变量,则会执行 realizeAllClasses() 函数
// Non-lazily realizes 所有已知 image 中所有未实现的类。(即对已知的 image 中的所有类:懒加载和非懒加载类全部进行实现)
if (DebugNonFragileIvars) {
realizeAllClasses();
}
//
// Print preoptimization statistics
// 打印预优化统计信息
if (PrintPreopt) {
...
}
#undef EACH_HEADER
}
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
上面我们已经把 _read_images
函数对整体实现都看完了,其中 Discover classes. Fix up unresolved future classes. Mark bundle classes.
部分的内容中涉及到如上调用,下面我们来详细看一下它:
/***********************************************************************
* 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().
* 请注意,此函数执行的所有工作都由 mustReadClasses() 预检。
* Do not change this function without updating that one.
*
* Locking: runtimeLock acquired by map_images or objc_readClassPair
**********************************************************************/
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
// 类的名字
const char *mangledName = cls->nonlazyMangledName();
// 1⃣️ 只有 superclass 不存在时,才会进入判断内
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();
// 2⃣️ 判断 class 是否是 unrealized future 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;
}
从上到下可看到有一些情况的处理:例如 superclass
不存在时、判断 class
是否是 unrealized future class
、判断该类是否禁用了预优化,而最终的绝大部分情况则会是调用:addNamedClass(cls, mangledName, replacing);
和 addClassTableEntry(cls);
下面我们看一下他们的实现。
/***********************************************************************
* addNamedClass
* Adds name => cls to the named non-meta class map.
* 将 name => cls 添加到命名为 non-meta class map。
* Warns about duplicate class names and keeps the old mapping.
* 警告:重复的类名并保留旧的 mapping。
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
runtimeLock.assertLocked();
Class old;
// 根据 name 查找对应的类(swift 类除外),
// 其中会在 NXMapTable *gdb_objc_realized_classes 中查找 和 dyld shared cache 的表中查找,
if ((old = getClassExceptSomeSwift(name)) && old != replacing) {
inform_duplicate(name, old, cls);
// getMaybeUnrealizedNonMetaClass uses name lookups.
// Classes not found by name lookup must be in the
// secondary meta->nonmeta table.
// 名称查找未找到的类必须位于辅助元->非元表中。
addNonMetaClass(cls);
} else {
// 把 cls 和 name 插入到 gdb_objc_realized_classes 中去
NXMapInsert(gdb_objc_realized_classes, name, cls);
}
ASSERT(!(cls->data()->flags & RO_META));
// wrong: constructed classes are already realized when they get here
// ASSERT(!cls->isRealized());
}
看注释可知道 addNamedClass
函数是把 name => cls
添加到命名为非元类的 map
中去。addNamedClass
函数内部则是首先 if ((old = getClassExceptSomeSwift(name)) && old != replacing)
是根据 name
去 NXMapTable *gdb_objc_realized_classes
和 dyld shared cache
的表中去查找对应对类,如果未找到的话则把 cls
插入到 gdb_objc_realized_classes
中去。
下面我们看一下 NXMapInsert(gdb_objc_realized_classes, name, cls);
的实现:
/* This module allows hashing of arbitrary associations [key -> value].
该模块允许对任意关联 [key -> value] 进行哈希化。
Keys and values must be pointers or integers, and client is responsible for allocating/deallocating this data.
键和值必须是指针或整数,client 负责 allocating/deallocating 这些数据。
A deallocation call-back is provided.
提供 deallocation call-back。
NX_MAPNOTAKEY (-1) is used internally as a marker, and therefore keys must always be different from -1.
NX_MAPNOTAKEY (-1) 在内部用作标记,因此 keys 必须始终不同于 -1。
As well-behaved scalable data structures, hash tables double in size when they start becoming full,
作为行为良好的可扩展数据结构,哈希表在开始变满时大小会增加一倍,从而保证平均恒定时间访问和线性大小。
thus guaranteeing both average constant time access and linear size. */
// 这里是 NXMapTable 的结构
typedef struct _NXMapTable {
/* private data structure; may change */
const struct _NXMapTablePrototype * _Nonnull prototype;
unsigned count;
unsigned nbBucketsMinusOne;
void * _Nullable buckets;
} NXMapTable OBJC_MAP_AVAILABILITY;
void *NXMapInsert(NXMapTable *table, const void *key, const void *value) {
// 取得 table 的 buckets 成员变量
MapPair *pairs = (MapPair *)table->buckets;
// 调用 table->prototype->hash 函数计算 key 在 table 中的哈希值
unsigned index = bucketOf(table, key);
// 取得 index 位置的 MapPair
MapPair *pair = pairs + index;
// key 不能等于 -1,-1 是保留值
if (key == NX_MAPNOTAKEY) {
_objc_inform("*** NXMapInsert: invalid key: -1\n");
return NULL;
}
// buckets 长度
unsigned numBuckets = table->nbBucketsMinusOne + 1;
// 上面如果根据 key 的哈希值取得的 pair,该 pair 的 key 是 -1,则表示该位置还没有存入东西,
// 则把 key 和 value 存在这里,如果当前 table 存储的数据已经超过了其容量的 3 / 4,则进行扩容并重新哈希化里面的数据
if (pair->key == NX_MAPNOTAKEY) {
pair->key = key; pair->value = value;
table->count++;
if (table->count * 4 > numBuckets * 3) _NXMapRehash(table);
return NULL;
}
// 如果 pair 的 key 和入参 key 相同,则表示 key 已经存在于 table 中(则更新 value)
if (isEqual(table, pair->key, key)) {
// 取得 pair 的 value,即旧值
const void *old = pair->value;
// 如果旧值和入参新值 value 不同,则把 value 赋值给 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);
// 再尝试插入 [key value]
return NXMapInsert(table, key, value);
} else {
// 如果进了这里,则表示是产生了哈希碰撞
// 用 index2 记录下入参 key 在 table 中的哈希值
unsigned index2 = index;
// 开放寻址法,nextInde 函数则是:(index + 1) & table->nbBucketsMinusOne,
// 直到 index2 等于 index,当 index2 等于 index 时表示寻址一遍了,都没有找到位置。
while ((index2 = nextIndex(table, index2)) != index) {
// 取得 index2 的 pair
pair = pairs + index2;
// 如果 pair 的 key 值是 -1,即表示为 [key value] 找到了一个空位置
if (pair->key == NX_MAPNOTAKEY) {
// 找到了空位,则把 [key value] 放入
pair->key = key; pair->value = value;
// count 自增
table->count++;
// 然后判断是否需要扩容,并把旧数据进行重新哈希化
if (table->count * 4 > numBuckets * 3) _NXMapRehash(table);
return NULL;
}
// 找到一个相同的 key,更新 value,并把旧值返回
if (isEqual(table, pair->key, key)) {
const void *old = pair->value;
if (old != value) pair->value = value;/* avoid writing unless needed! */
return (void *)old;
}
}
// 不可能发生这里,如果执行到了这里表示哈希表出错了,即 NXMapInsert 出现了 bug
/* no room: can't happen! */
_objc_inform("**** NXMapInsert: bug\n");
return NULL;
}
}
上面就是一个普通的哈希表插入的操作,最终将类的名字跟地址进行关联存储到 NXMapTable
中了。
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.
// 允许此类通过 shared cache 或 data segments 成为已知类,但不允许已经在动态表(dynamic table)中的类。
// 首先我们再看一眼 allocatedClasses,它是 objc 命名空间中的一个静态的 ExplicitInitDenseSet 类型,
// 里面装的是 Class 的一个 set。(其中包括类和元类)
// namespace objc {
// static ExplicitInitDenseSet allocatedClasses;
// }
// 然后在上面 runtime_init 函数中,调用了 objc::allocatedClasses.init();
// 对其进行了初始化。
auto &set = objc::allocatedClasses.get();
// 在 set 中找到 cls 的话触发断言
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))
// 把 cls 添加到 set 中
set.insert(cls);
// addMeta 为 true 时,则把 cls->ISA() 即 cls 的元类也添加到 set 中
if (addMeta)
// 看到这里递归调用 addClassTableEntry 且把 addMeta 参数置为 false
addClassTableEntry(cls->ISA(), false);
}
概括的说 load_images
函数就是用来调用类以及分类中的 +load
函数的(仅限于实现了 +load
函数的类或者分类)。
/***********************************************************************
* load_images
* Process +load in the given images which are being mapped in by dyld.
* 在 dyld 映射的给定 images 中处理 +load。
* Locking: write-locks runtimeLock and loadMethodLock
**********************************************************************/
// 下面是两个外联函数,一个用来判断 image 中是否有 load 函数,
// 另一个用来收集 image 中的 load 函数,然后后面会统一调用
extern bool hasLoadMethods(const headerType *mhdr);
extern void prepare_load_methods(const headerType *mhdr);
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// didInitialAttachCategories 标记加载分类的,默认值为 false,
// didCallDyldNotifyRegister 标记 _dyld_objc_notify_register 是否调用完成,
// 此时为 false,所以暂时此 if 内部不会执行。
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
// Return without taking locks if there are no +load methods here.
// 如果 mh 中不包含 +load 就直接不加锁 return(且 without taking locks)
// hasLoadMethods 函数是根据 `headerType *mhdr` 的 `__objc_nlclslist` 区和 `__objc_nlcatlist` 区中是否有数据,
// 来判断是否有 +load 函数要执行。(即是否包含非懒加载类和非懒加载分类)
if (!hasLoadMethods((const headerType *)mh)) return;
// loadMethodLock 是一把递归互斥锁(加锁)
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods发现 +load 方法
{
// runtimeLock 加锁
mutex_locker_t lock2(runtimeLock);
// 获取所有要调用的 +load 方法
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
// 调用获取到的所有 +load 方法
call_load_methods();
}
didCallDyldNotifyRegister
是一个定义在 objc/Source/objc-runtime-new.mm 文件中的全局变量,默认值为 false
,用来标记 _dyld_objc_notify_register
是否已经完成。然后在 objc/Project Headers/objc-private.h 中通过 extern
把 didCallDyldNotifyRegister
声明为一个外联变量,来给外部使用。然后最后是在 objc/Source/objc-os.mm 中的 _objc_init
函数内,在执行完 _dyld_objc_notify_register(&map_images, load_images, unmap_image);
函数后,把 didCallDyldNotifyRegister
置为了 true
。
/***********************************************************************
* didCallDyldNotifyRegister
* Whether the call to _dyld_objc_notify_register has completed.
**********************************************************************/
bool didCallDyldNotifyRegister = false;
#if __OBJC2__
extern bool didCallDyldNotifyRegister;
#endif
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
...
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
didInitialAttachCategories
是一个定义在 objc/Source/objc-runtime-new.mm 文件中的静态全局变量,默认值为 false
,且全局仅在 load_images
函数的起始处 if
内部被赋值为 true
,然后就一直为 true
了,就不会再进入该 if
了。即 didInitialAttachCategories
直白一点理解的话它即是用来标记 loadAllCategories();
函数有没有被调用过的。(即用来标记分类是否加载过用的)
/***********************************************************************
* didInitialAttachCategories
* Whether the initial attachment of categories present at startup has
* been done.
**********************************************************************/
static bool didInitialAttachCategories = false;
static void loadAllCategories() {
mutex_locker_t lock(runtimeLock);
for (auto *hi = FirstHeader; hi != NULL; hi = hi->getNext()) {
load_categories_nolock(hi);
}
}
其中loadAllCategories
里面是调用的load_categories_nolock
:
static void load_categories_nolock(header_info *hi) {
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
size_t count;
auto processCatlist = [&](category_t * const *catlist) {
for (unsigned i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
locstamped_category_t lc{cat, hi};
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Ignore the category.
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
if (cls->isStubClass()) {
// Stub classes are never realized. Stub classes
// don't know their metaclass until they're
// initialized, so we have to add categories with
// class methods or properties to the stub itself.
// methodizeClass() will find them and add them to
// the metaclass as appropriate.
if (cat->instanceMethods ||
cat->protocols ||
cat->instanceProperties ||
cat->classMethods ||
cat->protocols ||
(hasClassProperties && cat->_classProperties))
{
objc::unattachedCategories.addForClass(lc, cls);
}
} else {
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
if (cls->isRealized()) {
attachCategories(cls, &lc, 1, ATTACH_EXISTING);
} else {
//这个表就是分类表
objc::unattachedCategories.addForClass(lc, cls);
}
}
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
if (cls->ISA()->isRealized()) {
attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
} else {
// runtime_init的时候创建,第一部分有讲到
objc::unattachedCategories.addForClass(lc, cls->ISA());
}
}
}
}
};
processCatlist(hi->catlist(&count));
processCatlist(hi->catlist2(&count));
}
根据 headerType *mhdr
的 __objc_nlclslist
区和 __objc_nlcatlist
区中是否有数据,来判断是否有 +load
函数要执行。
// Quick scan for +load methods that doesn't take a lock.
bool hasLoadMethods(const headerType *mhdr)
{
size_t count;
// 读取 __objc_nlclslist 区中的非懒加载类的列表
if (_getObjc2NonlazyClassList(mhdr, &count) && count > 0) return true;
// 读取 __objc_nlcatlist 区中非懒加载分类的列表
if (_getObjc2NonlazyCategoryList(mhdr, &count) && count > 0) return true;
return false;
}
锁管理,在 objc/Source/objc-runtime-new.mm 文件的开头处,我们能看到如下几把锁,而其中的递归互斥锁 loadMethodLock
就是在 load_images
中使用的。
/***********************************************************************
* Lock management
**********************************************************************/
mutex_t runtimeLock;
mutex_t selLock;
#if CONFIG_USE_CACHE_LOCK
mutex_t cacheUpdateLock;
#endif
recursive_mutex_t loadMethodLock;
prepare_load_methods
用来获取所有要调用的 +load
方法(父类、子类、分类)。
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
// 获取所有 __objc_nlclslist 区的数据(所有非懒加载类)
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
// 由于其构造方式,此列表始终首先处理 superclasses 的 +load 函数
// 需要调用 +load 的 classes 列表
// 遍历这些非懒加载类,并将其 +load 函数添加到 loadable_classes 数组中,优先添加其父类的 +load 方法,
// 用于下面 call_load_methods 函数调用
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
// 获取所有 __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 列表
// 遍历这些分类,并将其 +load 方法添加到 loadable_categories 数组中保存
add_category_to_loadable_list(cat);
}
}
schedule_class_load
将其 +load
函数添加到 loadable_classes
数组中,优先添加其父类的 +load
方法。(用于后续的 call_load_methods
函数调用。)
/***********************************************************************
* prepare_load_methods
* Schedule +load for classes in this image, any un-+load-ed
* superclasses in other images, and any categories in this image.
**********************************************************************/
// 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;
// DEBUG 模式下的断言,cls 必须是实现过的(这个在 _read_images 中已经实现了)
ASSERT(cls->isRealized()); // _read_images should realize
// RW_LOADED 是 class +load 已被调用的掩码
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);
}
下面的 add_category_to_loadable_list
函数就不展开了,和 schedule_class_load
差不多。这里我们只要谨记 +load
函数的加载顺序就好了:父类 -> 子类 -> 分类。
/***********************************************************************
* call_load_methods
* Call all pending class and category +load methods.
* Class +load methods are called superclass-first.
* Category +load methods are not called until after the parent class's +load.
*
* This method must be RE-ENTRANT, because a +load could trigger
* more image mapping. In addition, the superclass-first ordering
* must be preserved in the face of re-entrant calls. Therefore,
* only the OUTERMOST call of this function will do anything, and
* that call will handle all loadable classes, even those generated
* while it was running.
*
* The sequence below preserves +load ordering in the face of
* image loading during a +load, and make sure that no
* +load method is forgotten because it was added during
* a +load call.
* Sequence:
* 1. Repeatedly call class +loads until there aren't any more
* 2. Call category +loads ONCE.
* 3. Run more +loads if:
* (a) there are more classes to load, OR
* (b) there are some potential category +loads that have
* still never been attempted.
* Category +loads are only run once to ensure "parent class first"
* ordering, even if a category +load triggers a new loadable class
* and a new loadable category attached to that class.
*
* Locking: loadMethodLock must be held by the caller
* All other locks must not be held.
**********************************************************************/
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.
// 重入调用什么都不做;最外层的调用将完成工作。
// 如果正在 loading 则 return,
// 保证当前 +load 方法同时只有一次被调用
if (loading) return;
loading = YES;
// 创建自动释放池
void *pool = objc_autoreleasePoolPush();
// 循环调用
do {
// 1. Repeatedly call class +loads until there aren't any more
// 调用类中的 +load 函数
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();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
// 如果 loadable_classes_used 大于 0,或者有更多分类需要调用 +load,则循环继续。(一般 loadable_classes_used 到这里基本就是 0 了)
// 自动释放池进行 pop
objc_autoreleasePoolPop(pool);
// 标记处理完成了,可以进行下一个了
loading = NO;
}
在 while
循环中,先循环遍历类、父类的 +load
函数,然后遍历 分类的 +load
方法。
/***********************************************************************
* call_class_loads
* Call all pending class +load methods.
* If new classes become loadable, +load is NOT called for them.
*
* Called only by call_load_methods().
**********************************************************************/
static void call_class_loads(void)
{
int i;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
//⚠️直接函数调用,没有msg_send
(*load_method)(cls, @selector(load));
}
// Destroy the detached list.
if (classes) free(classes);
}
通过这些分析可以总结出load方法一些特性:
main
函数