1._objc_init源碼分析
首先,我們直接取objc 源碼中找_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?
//讀取影響運(yùn)行時(shí)的環(huán)境變量,如果需要,還可以打開環(huán)境變量幫助 export OBJC_HELP = 1
environ_init();
// 關(guān)于線程 key 的綁定,例如線程數(shù)據(jù)的析構(gòu)函數(shù)
tls_init();
// 運(yùn)行C++靜態(tài)構(gòu)造函數(shù),在dyld調(diào)用我們的靜態(tài)析構(gòu)函數(shù)之前,libc會調(diào)用_objc_init(),因此我們必須自己做
static_init();
// runtime運(yùn)行時(shí)環(huán)境初始化,里面主要是unattachedCategories、allocatedClasses -- 分類初始化
runtime_init();
// 初始化libobjc的異常處理系統(tǒng)
exception_init();
// 緩存條件初始化
cache_init();
// 啟動回調(diào)機(jī)制,通常這不會做什么,因?yàn)樗械某跏蓟际嵌栊缘?,但是對于某些進(jìn)程,我們會迫不及待地加載trampolines dylib
_imp_implementationWithBlock_init();
/*
_dyld_objc_notify_register -- dyld 注冊的地方
- 僅供objc運(yùn)行時(shí)使用
- 注冊處理程序,以便在映射、取消映射 和初始化objc鏡像文件時(shí)使用,dyld將使用包含objc_image_info的鏡像文件數(shù)組,回調(diào) mapped 函數(shù)
map_images: dyld將image鏡像文件加載進(jìn)內(nèi)存時(shí),會觸發(fā)該函數(shù)
load_images:dyld初始化image會觸發(fā)該函數(shù)
unmap_image:dyld將image移除時(shí)會觸發(fā)該函數(shù)
*/
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
1.1 environ_init:環(huán)境變量初始化
environ_init方法的源碼如下,其中的關(guān)鍵代碼是 for 循環(huán):
void environ_init(void)
{
//...省略部分邏輯
if (PrintHelp || PrintOptions) {
//...省略部分邏輯
for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
const option_t *opt = &Settings[i];
if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env);
}
}
}
有以下兩種方式可以打印所有的環(huán)境變量:
-for循環(huán)之前的判斷條件去掉


- 通過
export OBJC_HELP = 1,打印環(huán)境變量
這些環(huán)境變量,均可以通過target -- Edit Scheme -- Run --Arguments -- Environment Variables配置,其中常用的環(huán)境變量主要有以下幾個:
DYLD_PRINT_STATISTICS:設(shè)置DYLD_PRINT_STATISTICS為YES,控制臺就會打印App 的加載時(shí)長,包括整體加載時(shí)長和動態(tài)庫加載時(shí)長,即main函數(shù)之前的啟動時(shí)間(查看pre-main耗時(shí)),可以通過設(shè)置了解其耗時(shí)部分,并對其進(jìn)行啟動優(yōu)化。OBJC_DISABLE_NONPOINTER_ISA:杜絕生成相應(yīng)的nonpointer isa(nonpointer isa指針地址末尾為 1),生成的都是普通的 isaOBJC_PRINT_LOAD_METHODS:打印Class及Category的+(void)load方法的調(diào)用信息
-NSDoubleLocalizedStrings:項(xiàng)目做國際化本地化(Localized)的時(shí)候是一個挺耗時(shí)的工作,想要檢測國際化翻譯好的語言文字UI會變成什么樣子,可以指定這個啟動項(xiàng)??梢栽O(shè)置 NSDoubleLocalizedStrings為YES。
-NSShowNonLocalizedStrings:在完成國際化的時(shí)候,偶爾會有一些字符串沒有做本地化,這時(shí)就可以設(shè)置NSShowNonLocalizedStrings 為YES,所有沒有被本地化的字符串全都會變成大寫。
1.1.1 環(huán)境變量 - OBJC_DISABLE_NONPOINTER_ISA
以OBJC_DISABLE_NONPOINTER_ISA為例,將其設(shè)置為YES,如下所示:

-
未設(shè)置
OBJC_DISABLE_NONPOINTER_ISA之前,isa地址的二進(jìn)制打印,末尾為 1:
image.jpeg -
設(shè)置
OBJC_DISABLE_NONPOINTER_ISA環(huán)境變量之后,末尾變成了 0:
image.jpeg
所以OBJC_DISABLE_NONPOINTER_ISA可以控制isa優(yōu)化開關(guān),從而優(yōu)化整個內(nèi)存結(jié)構(gòu)。
1.1.2 環(huán)境變量 - OBJC_PRINT_LOAD_METHODS
- 配置環(huán)境變量
OBJC_PRINT_LOAD_METHODS,設(shè)置為YES - 在
LGPerson類中重寫+load函數(shù),運(yùn)行之后,打印如下:
image.jpeg
所以,OBJC_PRINT_LOAD_METHODS可以監(jiān)控所有的+load方法,從而處理啟動優(yōu)化。
1.2tls_init: 線程 key 的綁定
主要是本地線程池的初始化以及析構(gòu),源碼如下:
void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS//本地線程池,用來進(jìn)行處理
pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);//初始init
#else
_objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);//析構(gòu)
#endif
}
1.3 static_init:運(yùn)行系統(tǒng)級別的C++靜態(tài)構(gòu)造函數(shù)
主要是運(yùn)行系統(tǒng)級別的C++靜態(tài)構(gòu)造函數(shù),在dyld調(diào)用我們的靜態(tài)構(gòu)造函數(shù)之前,libc調(diào)用_objc_init方法,即系統(tǒng)級別的C++構(gòu)造函數(shù)先于自定義的C++構(gòu)造函數(shù)運(yùn)行:
static void static_init()
{
size_t count;
auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
for (size_t i = 0; i < count; i++) {
inits[i]();
}
}
1.4runtime_init:運(yùn)行時(shí)環(huán)境初始化
這個主要是運(yùn)行時(shí)的初始化,主要分為兩部分:分類初始化、類的表初始化
void runtime_init(void)
{
objc::unattachedCategories.init(32);
objc::allocatedClasses.init(); //初始化 -- 開辟的類的表
}
1.5exception_init:初始化libobjc 的異常處理系統(tǒng)
主要是初始化libobjc的異常處理系統(tǒng),注冊異常處理的回調(diào),從而監(jiān)控異常的處理,源碼如下:
void exception_init(void)
{
old_terminate = std::set_terminate(&_objc_terminate);
}
- 當(dāng)有
crash發(fā)生時(shí),會來到_objc_terminate方法,走到uncaught_handler扔出異常
/***********************************************************************
* _objc_terminate
* Custom std::terminate handler.
*
* The uncaught exception callback is implemented as a std::terminate handler.
* 1. Check if there's an active exception
* 2. If so, check if it's an Objective-C exception
* 3. If so, call our registered callback with the object.
* 4. Finally, call the previous terminate handler.
**********************************************************************/
static void (*old_terminate)(void) = nil;
static void _objc_terminate(void)
{
if (PrintExceptions) {
_objc_inform("EXCEPTIONS: terminating");
}
if (! __cxa_current_exception_type()) {
// No current exception.
(*old_terminate)();
}
else {
// There is a current exception. Check if it's an objc exception.
@try {
__cxa_rethrow();
} @catch (id e) {
// It's an objc object. Call Foundation's handler, if any.
(*uncaught_handler)((id)e);//扔出異常
(*old_terminate)();
} @catch (...) {
// It's not an objc object. Continue to C++ terminate.
(*old_terminate)();
}
}
}
- 搜索
uncaught_handler,在app層會傳入一個函數(shù)用于處理異常,以便于調(diào)用函數(shù),然后回到原有的app層中,如下所示,其中fn為傳入的函數(shù),即uncaught_handler等于fn:
objc_uncaught_exception_handler
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
// fn為設(shè)置的異常句柄 傳入的函數(shù),為外界給的
objc_uncaught_exception_handler result = uncaught_handler;
uncaught_handler = fn; //賦值
return result;
}
crash分類
crash 的主要原因是收到了未處理的信號,主要來源于三個地方:
- kernel 內(nèi)核
- 其他進(jìn)程
- App 本身
所以相對應(yīng)的,crash 也分為了3 種:
-
Mach 異常:是指最底層的內(nèi)核級異常。用戶態(tài)的開發(fā)者可以直接通過Mach API設(shè)置thread,task,host的異常端口,來捕獲Mach異常 -
Unix 信號:又稱BSD 信號,如果開發(fā)者沒有捕獲Mach 異常,則會被host層的方法ux_exception()將異常轉(zhuǎn)換為對應(yīng)的UNIX信號,并通過方法threadsignal()將信號投遞到出錯線程??梢酝ㄟ^方法signal(x, SignalHandler)來捕獲single。 -
NSException 應(yīng)用級異常:它是未被捕獲的Objective-C異常,導(dǎo)致程序向自身發(fā)送了SIGABRT信號而崩潰,對于未捕獲的Objective-C異常,是可以通過try catch來捕獲的,或者通過NSSetUncaughtExceptionHandler()機(jī)制來捕獲。
針對應(yīng)用級異常,可以通過注冊異常捕獲的函數(shù),即NSSetUncaughtExceptionHandler機(jī)制,實(shí)現(xiàn)線程?;?,收集上傳日志。
應(yīng)用級crash 攔截
所以在開發(fā)中,會針對crash進(jìn)行攔截處理,即 app代碼中給一個異常句柄NSSetUncaughtExceptionHandler,傳入一個函數(shù)給系統(tǒng),當(dāng)異常發(fā)生后,調(diào)用函數(shù)(函數(shù)中可以線程?;?,收集并上傳崩潰日志),然后回到原有的 app層中,其本質(zhì)就是一個回調(diào)函數(shù),如下圖:

上述方式只適合收集應(yīng)用級異常,我們要做的就是用自定義的函數(shù)替代該ExceptionHandler即可。
1.6cache_init:緩存初始化
主要是緩存初始化,源碼如下:
void cache_init()
{
#if HAVE_TASK_RESTARTABLE_RANGES
mach_msg_type_number_t count = 0;
kern_return_t kr;
while (objc_restartableRanges[count].location) {
count++;
}
//為當(dāng)前任務(wù)注冊一組可重新啟動的緩存
kr = task_restartable_ranges_register(mach_task_self(),
objc_restartableRanges, count);
if (kr == KERN_SUCCESS) return;
_objc_fatal("task_restartable_ranges_register failed (result 0x%x: %s)",
kr, mach_error_string(kr));
#endif // HAVE_TASK_RESTARTABLE_RANGES
}
1.7_imp_implementationWithBlock_init : 啟動回調(diào)機(jī)制
該方法主要是啟動回調(diào)機(jī)制,通常這不會做什么,因?yàn)樗械某跏蓟际嵌栊缘?,但是對于某些進(jìn)程,我們會迫不及待地加載libobjc-trampolines.dylib,其源碼如下:
void
_imp_implementationWithBlock_init(void)
{
#if TARGET_OS_OSX
// Eagerly load libobjc-trampolines.dylib in certain processes. Some
// programs (most notably QtWebEngineProcess used by older versions of
// embedded Chromium) enable a highly restrictive sandbox profile which
// blocks access to that dylib. If anything calls
// imp_implementationWithBlock (as AppKit has started doing) then we'll
// crash trying to load it. Loading it here sets it up before the sandbox
// profile is enabled and blocks it.
// 在某些進(jìn)程中渴望加載libobjc-trampolines.dylib。一些程序(最著名的是嵌入式Chromium的較早版本使用的QtWebEngineProcess)啟用了嚴(yán)格限制的沙箱配置文件,從而阻止了對該dylib的訪問。如果有任何調(diào)用imp_implementationWithBlock的操作(如AppKit開始執(zhí)行的操作),那么我們將在嘗試加載它時(shí)崩潰。將其加載到此處可在啟用沙箱配置文件之前對其進(jìn)行設(shè)置并阻止它。
// This fixes EA Origin (rdar://problem/50813789)
// and Steam (rdar://problem/55286131)
if (__progname &&
(strcmp(__progname, "QtWebEngineProcess") == 0 ||
strcmp(__progname, "Steam Helper") == 0)) {
Trampolines.Initialize();
}
#endif
}
1.8_dyld_objc_notify_register: dyld注冊
這個方法的源碼實(shí)現(xiàn)在dyld源碼中,下面是_dyld_objc_notify_register這個方法的聲明:
//
// Note: only for use by objc runtime
// Register handlers to be called when objc images are mapped, unmapped, and initialized.
// Dyld will call back the "mapped" function with an array of images that contain an objc-image-info section.
// Those images that are dylibs will have the ref-counts automatically bumped, so objc will no longer need to
// call dlopen() on them to keep them from being unloaded. During the call to _dyld_objc_notify_register(),
// dyld will call the "mapped" function with already loaded objc images. During any later dlopen() call,
// dyld will also call the "mapped" function. Dyld will call the "init" function when dyld would be called
// initializers in that image. This is when objc calls any +load methods in that image.
//
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped);
從注釋中可以看出:
- 僅供
objc運(yùn)行時(shí)使用 - 注冊處理程序,以便于在鏡像映射、取消鏡像映射和初始化
objc時(shí)調(diào)用 -
dyld將會通過一個包含objc-image-info的鏡像文件的數(shù)組回調(diào)mapped函數(shù)
方法中的三個參數(shù)分別表示的含義如下:
-
map_images:
dyld將image(鏡像文件)加載進(jìn)內(nèi)存時(shí),會觸發(fā)該函數(shù) -
load_image:
dyld初始化 image 會觸發(fā)該函數(shù)
-upmap_image:
dyld將image移除時(shí),會觸發(fā)該函數(shù)
2. dyld 和objc 的關(guān)聯(lián)
其方法的源碼實(shí)現(xiàn)和調(diào)用如下,即dyld 和Objc的關(guān)聯(lián)可以通過源碼提現(xiàn):
===> libobjc源碼中--調(diào)用
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
??
===> dyld源碼--具體實(shí)現(xiàn)
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped)
{
dyld::registerObjCNotifiers(mapped, init, unmapped);
}
從上可以得出:
-
mapped等價(jià)于map_images
-init等價(jià)于load_images -
unmapped等價(jià)于unmap_image
在之前 dyld 加載流程中,我們知道了load_images是在 notifySingle 方法中,通過 sNotifyObjCInit調(diào)用的,如下所示:

然后通過查找sNotifyObjCInit,最終找到了 _dyld_objc_notify_register - > registerObjCNotifiers,在該方法中將_dyld_objc_notify_register傳入的參數(shù)賦值給了 3 個回調(diào)函數(shù):

所以有下面等價(jià)關(guān)系:
sNotifyObjCMapped == mapped == map_imagessNotifyObjCInit == init == load_images-
sNotifyObjCUnmapped == unmapped == unmap_image
image.jpeg
關(guān)于load_images的調(diào)用時(shí)機(jī)已經(jīng)在 dyld加載過程中講過了,下面以map_images為例,看看其調(diào)用時(shí)機(jī)
-
dyld中全局搜索sNotifyObjcMapped :registerObjCNotifiers -- notifyBatchPartial -- sNotifyObjCMapped
image.png -
全局搜索
notifyBatchPartial,在registerObjCNotifiers方法中調(diào)用
image.jpeg
所以有以下結(jié)論:map_images是先于load_images調(diào)用,即先map_images ,再load_images。
3. 總結(jié)
結(jié)合 dyld 加載流程,dyld 與 Objc 的關(guān)聯(lián)如下圖所示:

4. 環(huán)境變量匯總
| 環(huán)境變量名 | 說明 |
|---|---|
| OBJC_PRINT_OPTIONS | 輸出OBJC已設(shè)置的選項(xiàng) |
| OBJC_PRINT_IMAGES | 輸出已load的image信息 |
| OBJC_PRINT_LOAD_METHODS | 打印 Class 及 Category 的 + (void)load 方法的調(diào)用信息 |
| OBJC_PRINT_INITIALIZE_METHODS | 打印 Class 的 + (void)initialize 的調(diào)用信息 |
| OBJC_PRINT_RESOLVED_METHODS | 打印通過 +resolveClassMethod: 或 +resolveInstanceMethod: 生成的類方法 |
| OBJC_PRINT_CLASS_SETUP | 打印 Class 及 Category 的設(shè)置過程 |
| OBJC_PRINT_PROTOCOL_SETUP | 打印 Protocol 的設(shè)置過程 |
| OBJC_PRINT_IVAR_SETUP | 打印 Ivar 的設(shè)置過程 |
| OBJC_PRINT_VTABLE_SETUP | 打印 vtable 的設(shè)置過程 |
| OBJC_PRINT_VTABLE_IMAGES | 打印 vtable 被覆蓋的方法 |
| OBJC_PRINT_CACHE_SETUP | 打印方法緩存的設(shè)置過程 |
| OBJC_PRINT_FUTURE_CLASSES | 打印從 CFType 無縫轉(zhuǎn)換到 NSObject 將要使用的類(如 CFArrayRef 到 NSArray * ) |
| OBJC_PRINT_GC | 打印一些垃圾回收操作 |
| OBJC_PRINT_PREOPTIMIZATION | 打印 dyld 共享緩存優(yōu)化前的問候語 |
| OBJC_PRINT_CXX_CTORS | 打印類實(shí)例中的 C++ 對象的構(gòu)造與析構(gòu)調(diào)用 |
| OBJC_PRINT_EXCEPTIONS | 打印異常處理 |
| OBJC_PRINT_EXCEPTION_THROW | 打印所有異常拋出時(shí)的 Backtrace |
| OBJC_PRINT_ALT_HANDLERS | 打印 alt 操作異常處理 |
| OBJC_PRINT_REPLACED_METHODS | 打印被 Category 替換的方法 |
| OBJC_PRINT_DEPRECATION_WARNINGS | 打印所有過時(shí)的方法調(diào)用 |
| OBJC_PRINT_POOL_HIGHWATER | 打印 autoreleasepool 高水位警告 |
| OBJC_PRINT_CUSTOM_RR | 打印含有未優(yōu)化的自定義 retain/release 方法的類 |
| OBJC_PRINT_CUSTOM_AWZ | 打印含有未優(yōu)化的自定義 allocWithZone 方法的類 |
| OBJC_PRINT_RAW_ISA | 打印需要訪問原始 isa 指針的類 |
| OBJC_DEBUG_UNLOAD | 卸載有不良行為的 Bundle 時(shí)打印警告 |
| OBJC_DEBUG_FRAGILE_SUPERCLASSES | 當(dāng)子類可能被對父類的修改破壞時(shí)打印警告 |
| OBJC_DEBUG_FINALIZERS | 警告實(shí)現(xiàn)了 -dealloc 卻沒有實(shí)現(xiàn) -finalize 的類 |
| OBJC_DEBUG_NIL_SYNC | 警告 @synchronized(nil) 調(diào)用,這種情況不會加鎖 |
| OBJC_DEBUG_NONFRAGILE_IVARS | 打印突發(fā)地重新布置 non-fragile ivars 的行為 |
| OBJC_DEBUG_ALT_HANDLERS | 記錄更多的 alt 操作錯誤信息 |
| OBJC_DEBUG_MISSING_POOLS | 警告沒有 pool 的情況下使用 autorelease,可能內(nèi)存泄漏 |
| OBJC_DEBUG_DUPLICATE_CLASSES | 當(dāng)出現(xiàn)類重名時(shí)停機(jī) |
| OBJC_USE_INTERNAL_ZONE | 在一個專用的 malloc 區(qū)分配運(yùn)行時(shí)數(shù)據(jù) |
| OBJC_DISABLE_GC | 強(qiáng)行關(guān)閉自動垃圾回收,即使可執(zhí)行文件需要垃圾回收 |
| OBJC_DISABLE_VTABLES | 關(guān)閉 vtable 分發(fā) |
| OBJC_DISABLE_PREOPTIMIZATION | 關(guān)閉 dyld 共享緩存優(yōu)化前的問候語 |
| OBJC_DISABLE_TAGGED_POINTERS | 關(guān)閉 NSNumber 等的 tagged pointer 優(yōu)化 |
| OBJC_DISABLE_NONPOINTER_ISA | 關(guān)閉 non-pointer isa 字段的訪問 |





