
_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();
tls_init();
static_init();
runtime_init();
exception_init();
cache_init();
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
我們先不要著急分析流程,看到最后一行代碼:_dyld_objc_notify_register。這個很明顯是 _dyld 里面的一個方法,上一篇文章中分類了 _dyld 的加載流程。

所以這里從 _dyld 中將這個方法的源碼拿了過來,下面是 _dyld_objc_notify_register 的源碼
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);
}
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
// record functions to call
sNotifyObjCMapped = mapped;
sNotifyObjCInit = init;
sNotifyObjCUnmapped = unmapped;
……
}
源碼很簡單,僅僅是記錄了這三個方法的地址,在需要的時候進行一個調(diào)用。在我們平時開發(fā)中,需要用到地址傳遞的時候,一般是需要修改這個變量的值,那么可以猜測這個方法的作用是將 _dyld 中的 mappedImage 回傳給 objc,這里的對內(nèi)容放在后面分析,下面對這些初始化方法進行一個簡單的介紹。
environ_init();
讀取影響運行時的環(huán)境變量。如果需要,可以打印環(huán)境變量幫助。
1. 在終端中輸入 export OBJC_HELP=1 可以輸出所有環(huán)境變量

如果沒有輸出,嘗試再執(zhí)行一個 open 命令
2. 在Xcode 源碼中輸出環(huán)境變量
在這里手動修改:PrintHelp=true;

3. OBJC_DISABLE_NONPOINTER_ISA 環(huán)境變量
-
設(shè)置
OBJC_DISABLE_NONPOINTER_ISA為YESimage設(shè)置之后的打印出來的isa結(jié)果
image設(shè)置之前的isa結(jié)果
image因為系統(tǒng)做了優(yōu)化,對isa聯(lián)合體做了平移操作,所以會不一樣
OBJC_PRINT_LOAD_METHODS
4. 環(huán)境變量 OBJC_PRINT_LOAD_METHODS
-
設(shè)置 OBJC_PRINT_LOAD_METHODS 為 YES
image -
打印所有的 +load 方法
image
作用:可以對這些方法做方法優(yōu)化,避免全局搜索找到的load方法(可能沒有用到)
tls_init();
關(guān)于線程的綁定,比如每個線程數(shù)的析構(gòu)函數(shù)
static_init();
- 運行C++ 的靜態(tài)構(gòu)造函數(shù)。
- 在 _dyld 調(diào)用我們自己的靜態(tài)構(gòu)造函數(shù)之前調(diào)用
-
libc會調(diào)用_objc_init(),所以我們必須自己做。
runtime_init();
是空實現(xiàn)!就是說 objc 的鎖是完成采用C++那一套的, oc中不需要做任何處理。
exception_init();
初始化 libobjc 的異常處理系統(tǒng)。比如監(jiān)控下一行注冊異常的回調(diào)的代碼。
cache_init();
_imp_implementationWithBlock_init()
_dyld_objc_notify_register()
僅供 objc 運行時使用
注冊處理程序,以便在映射、取消映射和初始化 objc-image 時調(diào)用
Dyld將使用包含 objc-image-info 的景象文件的數(shù)組,回調(diào) mapped 函數(shù)
- 研究對象
- map_images(重點)
- load_images(重點)
- unmap_image:主要做卸載相關(guān)的操作,不做重點研究
_objc_init:map_images 流程
read_images 注釋
/***********************************************************************
* 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,這個函數(shù)的注釋說“處理給定的鏡像文件(在dyld中映射進去的鏡像)”,這句注釋正好驗證了我們上面的猜測——registerObjCNotifiers 方法的作用是將 _dyld 中的 mappedImage 回傳給 objc。
下面來分析 map_images_nolock 方法的流程
read_images 要讀懂的問題
- 我們的dyld主題的思維是加載庫-鏡像文件,但是鏡像文件怎么讀取的?
- 我們的macho里面的數(shù)據(jù)怎么到我們的內(nèi)存?
- 有沒有不在macho里面的數(shù)據(jù)同樣也可以在內(nèi)存找到?
- sel方法編號的加載?
- 什么是懶加載類和非懶加載類?
- 類是如何加載實現(xiàn)的 -ro-rw 的關(guān)系?
- 協(xié)議&分類里面的數(shù)據(jù)是如何加載的?
read_images 初體驗
在dyld的源碼中去查找_dyld_objc_notify_register方法實現(xiàn)

1. 如何開展dyld研究
- 看注釋 “// record functions to call”——記錄被調(diào)用的函數(shù)
- 可以看出來,如果要研究這三個參數(shù),和明顯要研究這三個函數(shù)被調(diào)用的地方
- 以第一個參數(shù)為例,找到第一個函數(shù)被調(diào)用的地方

2. 在objc源碼中分析,定位到_read_images方法
-
_objc_init->map_imagesimage -
map_images->map_images_nolockimage -
map_images_nolock->_read_imagesimage
3. 怎么分析 _read_images方法
這個方法有400多行,逐行讀肯定不行
我們先將所有有注釋的代碼塊折疊起來
-
方法的準備條件
image -
方法的流程
image我們可以看到每個代碼塊都有很標準的注釋和日志輸出,整個流程一目了然:
- 加載所有類到類的
gdb_objc_realized_classes表中 - 對所有類做重映射
- 將所有SEL都注冊到
namedSelecotors表中 - 修復(fù)函數(shù)指針遺留
- 將所有
Protocol都添加到protocol_map表中 - 對所有
Protocol做重映射 - 初始化所有非懶加載的類,進行
rw、ro等操作 - 遍歷已標記的懶加載的類,并做初始化操作
- 處理所有
Category,包括Class和Meta Class - 初始化所有未初始的類。
- 加載所有類到類的
4. 表的介紹
-
gdb_objc_realized_classes:無論是否實現(xiàn),只要不在dyld共享緩存中的已命名類的表 -
allocatedClasses:通過objc_allocateClassPair已分配的所有類(和元類)的表 -
namedSelecotors:方法編號的表 -
protocol_map:協(xié)議的表
題外話
- 為什么類的開頭是 NS
NextStep的簡稱
喬幫主曾經(jīng)被蘋果踢出去了,然后又回來了 - NX開頭的又代表什么含義
NX表示一種CPU的計數(shù),“禁止執(zhí)行的意思”
read_images 流程分析
第一流程:查找類。修復(fù)未解決的future類。標記 bundle類。

從編譯后的類列表中取出所有類, 獲取到的是一個
classref_t類型指針
classref_t *classlist = _getObjc2ClassList(hi, &count);-
遍歷數(shù)組中會去除
OS_dispatch_queue_concurrentOS_xpc_objectNSRunloop等系統(tǒng)類, 例如CFFoundationlibdispatch中的類, 以及自己創(chuàng)建的類.通過readClass函數(shù)獲取處理后的新類, 內(nèi)部主要操作 ro 和 rw 結(jié)構(gòu)體 -
初始化所有懶加載的類需要的內(nèi)存空間,現(xiàn)在數(shù)據(jù)沒有加載
image
第二流程:修復(fù)類的重映射(一般不會走進來)

注釋
- 類列表和非懶加載類類表保持未重映射
- 重映射類和super類,用于消息分發(fā)
- 將未映射class和super class重映射,被remap的類都是非懶加載類
第三流程:修復(fù)SEL引用

將所有 SEL 都注冊到 namedSelecotors 表中
第四流程:修復(fù)舊的objc_msgSend_fixup調(diào)用站點,有條件才進來,不做分析

第五流程:查找協(xié)議protocols。修復(fù)協(xié)議protocols引用

從 _getObjc2ProtocolList 讀協(xié)議存到 protocol_map 中
第六流程:修復(fù)協(xié)議的引用

預(yù)先優(yōu)化的圖像可能已經(jīng)在正確的位置了,但并不能確定。所以需要重新映射協(xié)議的引用
第七流程:實現(xiàn)非懶加載類(重點流程)
- 實現(xiàn)了load方法的類
- 靜態(tài)實例變量的類(常見的是單例)

第八流程:實現(xiàn)未來類。(條件依賴第一個流程)

第九流程:查找分類。(默認找的是懶加載分類-實現(xiàn)了load)
- 注冊分類并關(guān)聯(lián)目標類
- 重新構(gòu)建類的方法列表(attachLists流程)
因為分類的方法也是放到了類的方法列表里面,而且是通過 attachLists 流程,插入了方法列表的前面,所以會造成分類方法“覆蓋”主類方法的現(xiàn)象。

第十流程:實現(xiàn)所有未實現(xiàn)的類











