07--應(yīng)用加載02--應(yīng)用加載流程[_objc_init][read_images]

TOC

_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 的加載流程。

App加載分析

所以這里從 _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)境變量

OBJC_HELP

如果沒有輸出,嘗試再執(zhí)行一個 open 命令

2. 在Xcode 源碼中輸出環(huán)境變量

在這里手動修改:PrintHelp=true;

image

3. OBJC_DISABLE_NONPOINTER_ISA 環(huán)境變量

  • 設(shè)置 OBJC_DISABLE_NONPOINTER_ISAYES

    image

    設(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 要讀懂的問題

  1. 我們的dyld主題的思維是加載庫-鏡像文件,但是鏡像文件怎么讀取的?
  2. 我們的macho里面的數(shù)據(jù)怎么到我們的內(nèi)存?
  3. 有沒有不在macho里面的數(shù)據(jù)同樣也可以在內(nèi)存找到?
  4. sel方法編號的加載?
  5. 什么是懶加載類和非懶加載類?
  6. 類是如何加載實現(xiàn)的 -ro-rw 的關(guān)系?
  7. 協(xié)議&分類里面的數(shù)據(jù)是如何加載的?

read_images 初體驗

在dyld的源碼中去查找_dyld_objc_notify_register方法實現(xiàn)

image

1. 如何開展dyld研究

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

2. 在objc源碼中分析,定位到_read_images方法

  • _objc_init->map_images

    image
  • map_images->map_images_nolock

    image
  • map_images_nolock->_read_images

    image

3. 怎么分析 _read_images方法

這個方法有400多行,逐行讀肯定不行

  1. 我們先將所有有注釋的代碼塊折疊起來

  2. 方法的準備條件

    image
  3. 方法的流程

    image

    我們可以看到每個代碼塊都有很標準的注釋和日志輸出,整個流程一目了然:

    1. 加載所有類到類的 gdb_objc_realized_classes 表中
    2. 對所有類做重映射
    3. 將所有SEL都注冊到 namedSelecotors 表中
    4. 修復(fù)函數(shù)指針遺留
    5. 將所有 Protocol 都添加到 protocol_map 表中
    6. 對所有 Protocol 做重映射
    7. 初始化所有非懶加載的類,進行 rw、ro等操作
    8. 遍歷已標記的懶加載的類,并做初始化操作
    9. 處理所有 Category,包括 ClassMeta Class
    10. 初始化所有未初始的類。

4. 表的介紹

  • gdb_objc_realized_classes:無論是否實現(xiàn),只要不在 dyld 共享緩存中的已命名類的表
  • allocatedClasses:通過 objc_allocateClassPair 已分配的所有類(和元類)的表
  • namedSelecotors:方法編號的表
  • protocol_map:協(xié)議的表

題外話

  1. 為什么類的開頭是 NS
    NextStep的簡稱
    喬幫主曾經(jīng)被蘋果踢出去了,然后又回來了
  2. NX開頭的又代表什么含義
    NX表示一種CPU的計數(shù),“禁止執(zhí)行的意思”

read_images 流程分析

第一流程:查找類。修復(fù)未解決的future類。標記 bundle類。

流程代碼
  1. 從編譯后的類列表中取出所有類, 獲取到的是一個 classref_t 類型指針
    classref_t *classlist = _getObjc2ClassList(hi, &count);

  2. 遍歷數(shù)組中會去除 OS_dispatch_queue_concurrent OS_xpc_object NSRunloop 等系統(tǒng)類, 例如 CFFoundation libdispatch 中的類, 以及自己創(chuàng)建的類.

    通過readClass函數(shù)獲取處理后的新類, 內(nèi)部主要操作 ro 和 rw 結(jié)構(gòu)體

  3. 初始化所有懶加載的類需要的內(nèi)存空間,現(xiàn)在數(shù)據(jù)沒有加載

    image

第二流程:修復(fù)類的重映射(一般不會走進來)

image

注釋

  1. 類列表和非懶加載類類表保持未重映射
  2. 重映射類和super類,用于消息分發(fā)
  3. 將未映射class和super class重映射,被remap的類都是非懶加載類

第三流程:修復(fù)SEL引用

image

將所有 SEL 都注冊到 namedSelecotors 表中

第四流程:修復(fù)舊的objc_msgSend_fixup調(diào)用站點,有條件才進來,不做分析

image

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

image

_getObjc2ProtocolList 讀協(xié)議存到 protocol_map

第六流程:修復(fù)協(xié)議的引用

image

預(yù)先優(yōu)化的圖像可能已經(jīng)在正確的位置了,但并不能確定。所以需要重新映射協(xié)議的引用

第七流程:實現(xiàn)非懶加載類(重點流程)

  • 實現(xiàn)了load方法的類
  • 靜態(tài)實例變量的類(常見的是單例)
image

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

image

第九流程:查找分類。(默認找的是懶加載分類-實現(xiàn)了load)

  • 注冊分類并關(guān)聯(lián)目標類
  • 重新構(gòu)建類的方法列表(attachLists流程)

因為分類的方法也是放到了類的方法列表里面,而且是通過 attachLists 流程,插入了方法列表的前面,所以會造成分類方法“覆蓋”主類方法的現(xiàn)象。

image

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

image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容