iOS底層之類的載入

語言: CN / TW / HK

iOS 全網最新objc4 可調式/編譯原始碼
編譯好的原始碼的下載地址

序言

前面的文章中探究了類的結構,知道了類中都有哪些內容,那麼今天就來探究一下,類到底是怎麼載入進記憶體的呢?在什麼時候載入到記憶體的呢?

我們定義的類.h.m檔案,首先需要通過編譯器生產可執行檔案,這個過程稱為編譯階段,然後安裝在裝置上載入執行。

編譯

  • 預編譯:編譯之前的一些先前的處理工作,處理一些#開頭的檔案,#include#define以及條件編譯等;
  • 編譯:對預編譯後的檔案進行詞法分析語法分析語義分析,並進行程式碼優化,生成彙編程式碼;
  • 彙編:將彙編檔案程式碼轉換為機器可以執行的指令,並生成目標檔案.o
  • 連結:將所有目標檔案以及連結的第三方庫,連結成可執行檔案macho;這一過程中,連結器將不同的目標檔案連結起來,因為不同的目標檔案之間可能有相互引用的變數或呼叫的函式,比如我們常用的系統庫。

動態方法決議-匯出.png

動態庫與靜態庫

  • 靜態庫:連結階段將彙編生成的目標檔案和引用庫一起連結打包到可執行檔案中,如:.a.lib
    • 優點:編譯成功後可執行檔案可以獨立執行,不需要依賴外部環境;
    • 缺點:編譯的檔案會變大,如果靜態庫更新必須重新編譯;
  • 動態庫:連結時不復制,程式執行時由系統載入到記憶體中,供系統呼叫,如:.dylib.framework
    • 優點:系統只需載入一次,多次使用,共用節省記憶體,通過更新動態庫,達到更新程式的目的;
    • 缺點:可執行檔案不可以單獨執行,必須依賴外部環境;

系統的framework是動態的,開發者建立的framework是靜態的

3864017-0c111edb6fed43b2.webp

dyld動態連結器

  • dyld是iOS作業系統的一個重要組成部分,在系統核心做好程式準備工作之後,會交由dyld負責餘下的工作。
  • dyld的作用:載入各個庫,也就是image映象檔案,由dyld從記憶體中讀到表中,載入主程式,link連結各個動靜態庫,進行主程式的初始化工作。

3864017-0c1deb5c8c79c239.png

dyld負責連結、載入程式,但是dyld的探索過程比較繁瑣就不詳細展開了,直接進入類的載入核心_objc_init方法探索。

_objc_init探索

```C++ 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(); // 關於執行緒key的繫結     static_init(); // 執行C++靜態建構函式     runtime_init(); // runtime執行時環境初始化     exception_init();// 初始化libobjc庫的異常處理

if OBJC2

cache_t::init(); // 快取條件初始化

endif

_imp_implementationWithBlock_init(); // 啟動回撥機制

// 註冊處理程式,以便在對映、取消對映和初始化objc影象時呼叫,僅供執行時Runtime使用

_dyld_objc_notify_register(&map_images, load_images, unmap_image);

if OBJC2

didCallDyldNotifyRegister = true;

endif

} ```

environ_init環境變數

```C++ /********** * environ_init * Read environment variables that affect the runtime. * Also print environment variable help, if requested. ********** */ void environ_init(void)  { // 部分核心程式碼 // Print OBJC_HELP and OBJC_PRINT_OPTIONS output.     if (PrintHelp  ||  PrintOptions) {         if (PrintHelp) {             _objc_inform("Objective-C runtime debugging. Set variable=YES to enable.");             _objc_inform("OBJC_HELP: describe available environment variables");             if (PrintOptions) {                 _objc_inform("OBJC_HELP is set");             }             _objc_inform("OBJC_PRINT_OPTIONS: list which options are set");         }

if (PrintOptions) {             _objc_inform("OBJC_PRINT_OPTIONS is set");         }

for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {             const option_t opt = &Settings[i]; //            if (opt->internal //                && !os_variant_allows_internal_security_policies("com.apple.obj-c")) //                continue;             if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);             if (PrintOptions && opt->var) _objc_inform("%s is set", opt->env);         }     } }

`` 通過控制PrintHelpPrintOptions可以列印當前環境變數的配置資訊,我們在原始碼環境中把for`迴圈程式碼複製出來改一下,執行

image.png

image.png

也可以通過終端命令export OBJC_HELP = 1,在終端上顯示

image.png

可以通過Edit shceme,在Environment Variables配置相關變數

  • OBJC_DISABLE_NONPOINTER_ISA: isa的優化開關,如果YES表示不使用,就是存指標;如果NO開啟指標優化,為nonpointer isa;
  • OBJC_PRINT_LOAD_METHODS:是否開啟列印所有load方法,可以判斷哪些類使用了load方法,做相應的優化處理,以優化啟動速度;

image.png

tls_init執行緒key的繫結

tls_init關於執行緒key的繫結,比如每個執行緒資料的解構函式 ```C++ 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

} ```

static_init執行C++靜態建構函式

執行C++靜態建構函式。 libcdyld呼叫靜態建構函式之前呼叫objc_init(),因此我們必須自己執行。 ```C++ static void static_init() {     size_t count1;     auto inits = getLibobjcInitializers(&_mh_dylib_header, &count1);     for (size_t i = 0; i < count1; i++) {         initsi;     }     size_t count2;     auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count2);     for (size_t i = 0; i < count2; i++) {         UnsignedInitializer init(offsets[i]);         init();     }

if DEBUG

if (count1 == 0 && count2 == 0)         _objc_inform("No static initializers found in libobjc. This is unexpected for a debug build. Make sure the 'markgc' build phase ran on this dylib. This process is probably going to crash momentarily due to using uninitialized global data.");

endif

} ```

runtime_init執行時環境初始化

Runtime執行時環境初始化,主要是unattachedCategoriesallocatedClasses兩張表的初始化 C++ void runtime_init(void) {     objc::disableEnforceClassRXPtrAuth = DisableClassRXSigningEnforcement;     objc::unattachedCategories.init(32); // 分類表     objc::allocatedClasses.init(); // 已開闢類的表 }

exception_init 異常系統初始化

初始化libobjc的異常處理系統,由map_images()呼叫。註冊異常處理的回撥,從而監控異常的處理 C++ void exception_init(void) {     old_terminate = std::set_terminate(&_objc_terminate); } 異常處理系統初始化後,當程式執行不符合底層規則時,比如:陣列越界方法未實現等,系統就會發出異常訊號。

image.png 有異常發生時,uncaught_handler函式會把異常資訊e丟擲

image.png uncaught_handler就是這裡傳進來的fn,這個fn就是我們檢測異常的控制代碼。我們可以自定義異常處理類,通過NSSetUncaughtExceptionHandler把我們控制代碼函式地址傳進去

image.png 有異常發生時,系統會回撥給我們異常exception,然後自定義上傳等處理操作。 image.png

cache_t::init快取條件初始化

```C++ void cache_t::init() {

if HAVE_TASK_RESTARTABLE_RANGES

mach_msg_type_number_t count = 0;     kern_return_t kr;

while (objc_restartableRanges[count].location) {         count++;     }     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

} ``` image.png

_imp_implementationWithBlock_init

通常情況下,這沒有任何作用,因為所有的初始化都是惰性的,但對於某些程序,我們急切地載入trampolines dylib。

在某些過程中急切地載入libobjc-tropolines.dylib。一些程式(最著名的是早期版本的嵌入式Chromium使用的QtWebEngineProcess)啟用了一個限制性很強的沙盒配置檔案,該檔案阻止對該dylib的訪問。如果有任何東西呼叫imp_implementationWithBlock(正如AppKit已經開始做的那樣),那麼我們將在嘗試載入它時崩潰。在這裡載入它會在啟用沙盒配置檔案並阻止它之前設定它。

```C++ 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.     //     // 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

} ```

_dyld_objc_notify_register

C++ void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,                                 _dyld_objc_notify_init      init,                                 _dyld_objc_notify_unmapped  unmapped);

_dyld_objc_notify_register中的三個引數含義如下 - &map_imagesdyldimage載入到記憶體中會呼叫該函式 - load_imagesdyld初始化所有的image檔案會呼叫 - unmap_image:將image移除時會呼叫

我們重點看的就是將image載入到記憶體中呼叫的函式map_image

image.pngmap_image中呼叫map_images_nolock

image.png map_images_nolock中的程式碼比較多,我們這裡直接看重點_read_images

_read_images解讀

_read_images方法中有360行程式碼,有點長,把裡面的大括號摺疊,蘋果的程式碼流程和註釋是很好的,可以先整體把握一下,裡面的ts.log很清晰的告訴了我們整個流程。 ``C++ void _read_images(header_info hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) {   ... //表示省略部分程式碼 #define EACH_HEADER \     hIndex = 0;         \     hIndex < hCount && (hi = hList[hIndex]); \     hIndex++ // 條件控制進行一次的載入     if (!doneOnce) { ... } // 修復預編譯階段的@selector`的混亂的問題 // 就是不同類中有相同的方法 但是相同的方法地址是不一樣的     // Fix up @selector references     static size_t UnfixedSelectors;     { ... }     ts.log("IMAGE TIMES: fix up selector references");

// 錯誤混亂的類處理

// Discover classes. Fix up unresolved future classes. Mark bundle classes.     bool hasDyldRoots = dyld_shared_cache_some_image_overridden();     for (EACH_HEADER) { ... }     ts.log("IMAGE TIMES: discover classes");

// 修復重對映一些沒有被映象檔案載入進來的類

// Fix up remapped classes     // Class list and nonlazy class list remain unremapped.     // Class refs and super refs are remapped for message dispatching.     if (!noClassesRemapped()) { ... }     ts.log("IMAGE TIMES: remap classes");

if SUPPORT_FIXUP

// 修復一些訊息

// Fix up old objc_msgSend_fixup call sites     for (EACH_HEADER) { ... }     ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");

endif

// 當類中有協議時:`readProtocol`

// Discover protocols. Fix up protocol refs.     for (EACH_HEADER) { ... }     ts.log("IMAGE TIMES: discover protocols");

// 修復沒有被載入的協議

// Fix up @protocol references     // Preoptimized images may have the right      // answer already but we don't know for sure.     for (EACH_HEADER) { ... }     ts.log("IMAGE TIMES: fix up @protocol references");

// 分類的處理

// Discover categories. Only do this after the initial category     // attachment has been done. For categories present at startup,     // discovery is deferred until the first load_images call after     // the call to _dyld_objc_notify_register completes.
    if (didInitialAttachCategories) { ... }     ts.log("IMAGE TIMES: discover categories");

// 類的載入處理

// Category discovery MUST BE Late to avoid potential races     // when other threads call the new category code befor     // this thread finishes its fixups.     // +load handled by prepare_load_methods()     // Realize non-lazy classes (for +load methods and static instances)     for (EACH_HEADER) { ... }     ts.log("IMAGE TIMES: realize non-lazy classes");

// 沒有被處理的類,優化那些被侵犯的類

// Realize newly-resolved future classes, in case CF manipulates them     if (resolvedFutureClasses) { ... }     ts.log("IMAGE TIMES: realize future classes");    ...

undef EACH_HEADER

} ```

  • 條件控制,進行一次載入;
  • 修復預編譯階段的@selecter混亂問題;
  • 錯誤混亂的類處理;
  • 修復重新對映一些沒有被映象檔案載入進來的類;
  • 修復一些訊息
  • 當類裡面有協議的時候:readProtocol
  • 修復沒有被載入進來的協議;
  • 分類處理;
  • 類的載入處理;
  • 沒有被處理的類

#### doneOnce載入一次 ```C++ if (!doneOnce) {         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

endif

if (DisableTaggedPointers) {             disableTaggedPointers();         } // 小物件地址混淆         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 // 容量:總數 * 4 / 3         int namedClassesSize =              (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3; // 建立雜湊表,用於存放所有的類         gdb_objc_realized_classes =             NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);         ts.log("IMAGE TIMES: first time tasks");     } `` 通過對doneOnce的判斷,只會進來一次條件語句,這裡主要處理對類表的開闢建立處理gdb_objc_realized_classes`。

修復@selecter混亂問題

```C++ static size_t UnfixedSelectors;     {         mutex_locker_t lock(selLock);         for (EACH_HEADER) {             if (hi->hasPreoptimizedSelectors()) continue;

bool isBundle = hi->isBundle(); // 從macho檔案中獲取方法名列表             SEL sels = _getObjc2SelectorRefs(hi, &count);             UnfixedSelectors += count;             for (i = 0; i < count; i++) {                 const char name = sel_cname(sels[i]); // sel通過name從dyld中查詢獲取                 SEL sel = sel_registerNameNoLock(name, isBundle);                 if (sels[i] != sel) { // 修復地址,以dyld為準                     sels[i] = sel;                 }             }         }     }

ts.log("IMAGE TIMES: fix up selector references"); `` 對sel進行修復,因為從編譯後的macho讀取的sel地址不一定是真實的sel`地址,在這裡做修復。

錯誤混亂的類處理

```C++ // Discover classes. Fix up unresolved future classes. Mark 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()             continue;         }         // 從macho中讀取的類列表         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,將cls的類名和地址做關聯             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.                 resolvedFutureClasses = (Class *)                     realloc(resolvedFutureClasses,                              (resolvedFutureClassCount+1) * sizeof(Class));            resolvedFutureClasses[resolvedFutureClassCount++] = newCls;             }         }     }     ts.log("IMAGE TIMES: discover classes"); `` - 通過_getObjc2ClassList從macho中讀取的所有的類; - 遍歷所有的類,通過readClass`講類的地址和類名關聯;

image.png

  • popFutureNamedClass返回已實現的類,我們新增的類未實現,這裡if語句不成立;
  • mangledName是有值的,呼叫addNamedClassname=>cls新增到命名的非元類對映中。
  • addClassTableEntry:將類新增到所有類的表中。如果addMeta為true,則自動新增類的元類。
  • 返回已處理的類

可以看出readClass函式是把傳進來的cls重新對映並新增cls和其元類到所有的類中。

修復重新對映的類

類列表和非懶載入類列表仍然未被新增。 類引用和父類引用被重新對映以用於訊息排程。 C++ 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]);             }         }     }     ts.log("IMAGE TIMES: remap classes");

修復一些訊息

```C++ // Fix up old objc_msgSend_fixup call sites | 修復舊的objc_msgSend_fixup呼叫站點     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++) {             // 內部將常用的alloc、objc_msgSend等函式指標進行註冊,並fix為新的函式指標             fixupMessageRef(refs+i);         }     }

ts.log("IMAGE TIMES: fix up objc_msgSend_fixup"); ```

image.png

新增協議

當類裡面有協議的時候,呼叫readProtocol繫結協議 ```C++ // 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);         }     }

ts.log("IMAGE TIMES: discover protocols"); ```

image.png

修復協議列表引用

上面做了協議和類的關聯,這裡是對協議進行重新對映

image.png

分類的處理

```C++ if (didInitialAttachCategories) {         for (EACH_HEADER) {             load_categories_nolock(hi);         }     }

ts.log("IMAGE TIMES: discover categories"); ``` 分類的流程是比較重要的,將開展新的文章專講一下。

類的載入處理

```C++ // Realize non-lazy classes (for +load methods and static instances) // 實現非懶載入類(實現了+load或靜態例項方法)     for (EACH_HEADER) {         // 通過_getObjc2NonlazyClassList獲取所有非懶載入類         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             }             // 對類cls執行首次初始化,包括分配其讀寫資料。不執行任何Swift端初始化。             realizeClassWithoutSwift(cls, nil);         }     }

ts.log("IMAGE TIMES: realize non-lazy classes"); ``` 本文重點

  • 呼叫nlclslist(裡面是呼叫_getObjc2NonlazyClassList)獲取所有非懶載入(non-lazy)的類;
  • 迴圈實現,再次新增到所有的類表中,如果已新增就不會新增進去,確保整個結構都被新增;
  • 呼叫realizeClassWithoutSwift對類cls執行首次初始化,包括分配其讀寫資料;

通過realizeClassWithoutSwift實現所有非懶載入類的第一次初始化,那我們就看realizeClassWithoutSwift如何實現的 ```C++ static Class realizeClassWithoutSwift(Class cls, Class previously) {     runtimeLock.assertLocked();

class_rw_t *rw;     Class supercls;     Class metacls;

if (!cls) return nil;     if (cls->isRealized()) {         // 驗證已實現的類         validateAlreadyRealizedClass(cls);         return cls;     }     ASSERT(cls == remapClass(cls));

// fixme verify class is not in an un-dlopened part of the shared cache?

auto ro = cls->safe_ro();     auto isMeta = ro->flags & RO_META; // 是否元類     if (ro->flags & RO_FUTURE) { // future類,rw的data已經初始化         // This was a future class. rw data is already allocated.         rw = cls->data();         ro = cls->data()->ro();         ASSERT(!isMeta);         cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);     } else {         // Normal class. Allocate writeable class data.         rw = objc::zalloc();         rw->set_ro(ro);         rw->flags = RW_REALIZED|RW_REALIZING|isMeta;         cls->setData(rw);     }

cls->cache.initializeToEmptyOrPreoptimizedInDisguise();

if FAST_CACHE_META

if (isMeta) cls->cache.setBit(FAST_CACHE_META);

endif

// Choose an index for this class.     // Sets cls->instancesRequireRawIsa if indexes no more indexes are available     cls->chooseClassArrayIndex();

if (PrintConnecting) {         _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",                      cls->nameForLogging(), isMeta ? " (meta)" : "",                       (void*)cls, ro, cls->classArrayIndex(),                      cls->isSwiftStable() ? "(swift)" : "",                      cls->isSwiftLegacy() ? "(pre-stable swift)" : "");     }

//     Realize superclass and metaclass, if they aren't already. //     This needs to be done after RW_REALIZED is set above, for root classes. //     This needs to be done after class index is chosen, for root metaclasses. //     This assumes that none of those classes have Swift contents, //       or that Swift's initializers have already been called. //       fixme that assumption will be wrong if we add support //       for ObjC subclasses of Swift classes.     // 實現父類和元類     supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);     metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil); // 純isa還是nonpointer isa

if SUPPORT_NONPOINTER_ISA

if (isMeta) {         // Metaclasses do not need any features from non pointer ISA         // This allows for a faspath for classes in objc_retain/objc_release.         cls->setInstancesRequireRawIsa(); // 純指標isa     } else {         // Disable non-pointer isa for some classes and/or platforms.         // Set instancesRequireRawIsa.         bool instancesRequireRawIsa = cls->instancesRequireRawIsa();         bool rawIsaIsInherited = false;         static bool hackedDispatch = false;         if (DisableNonpointerIsa) {             // Non-pointer isa disabled by environment or app SDK version             instancesRequireRawIsa = true;         }         else if (!hackedDispatch  &&  0 == strcmp(ro->getName(), "OS_object"))         {             // hack for libdispatch et al - isa also acts as vtable pointer             hackedDispatch = true;             instancesRequireRawIsa = true;         }         else if (supercls  &&  supercls->getSuperclass()  &&                  supercls->instancesRequireRawIsa())         {             // This is also propagated by addSubclass()             // but nonpointer isa setup needs it earlier.             // Special case: instancesRequireRawIsa does not propagate             // from root class to root metaclass             instancesRequireRawIsa = true;             rawIsaIsInherited = true;         }

if (instancesRequireRawIsa) {  // 純指標isa             cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);         }     } // SUPPORT_NONPOINTER_ISA

endif

// Update superclass and metaclass in case of remapping     cls->setSuperclass(supercls); // 設定superclass指向父類     cls->initClassIsa(metacls);   // 設定isa指向元類

// Reconcile instance variable offsets / layout.     // This may reallocate class_ro_t, updating our ro variable.     if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

// Set fastInstanceSize if it wasn't set already.     cls->setInstanceSize(ro->instanceSize);

// Copy some flags from ro to rw     if (ro->flags & RO_HAS_CXX_STRUCTORS) {         cls->setHasCxxDtor();         if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {             cls->setHasCxxCtor();         }     }

// Propagate the associated objects forbidden flag from ro or from     // the superclass.     if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||         (supercls && supercls->forbidsAssociatedObjects()))     {         rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;     }

// Connect this class to its superclass's subclass lists     if (supercls) {         addSubclass(supercls, cls);     } else {         addRootClass(cls);     }

// Attach categories // 方法、屬性、協議、分類的實現     methodizeClass(cls, previously);     return cls; } ``initClassIsa時會根據nonpointer`區別設定

  • 先類判斷是否已經實現,如果已實現通過validateAlreadyRealizedClass驗證;
  • 未實現,cls->setData(rw)處理類的data也就是rwro,初始化rw並拷貝ro資料到rw中;
  • 將事件往上層傳遞,來實現父類以及元類
  • 判斷isa指標型別,是純指標還是nonpointer指標,在設定isa時有不同;
  • cls->setSuperclass(supercls):設定superclass指向父類;
  • cls->initClassIsa(metacls):設定isa指向元類;
  • methodizeClass:在這裡進行方法、屬性、協議、分類的實現;

image.png

看一下methodizeClass的實現 ```C++ static void methodizeClass(Class cls, Class previously) {     runtimeLock.assertLocked();

bool isMeta = cls->isMetaClass();     auto rw = cls->data();     auto ro = rw->ro();     auto rwe = rw->ext();

// Methodizing for the first time     if (PrintConnecting) {         _objc_inform("CLASS: methodizing class '%s' %s",                       cls->nameForLogging(), isMeta ? "(meta)" : "");     }

// Install methods and properties that the class implements itself.     // rwe:方法、屬性、協議     method_list_t list = ro->baseMethods;     if (list) { // 寫入方法,並對方法進行排序         prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr*);         if (rwe) rwe->methods.attachLists(&list, 1);     }

property_list_t *proplist = ro->baseProperties;     if (rwe && proplist) {         rwe->properties.attachLists(&proplist, 1);     }

protocol_list_t *protolist = ro->baseProtocols;     if (rwe && protolist) {         rwe->protocols.attachLists(&protolist, 1);     }

// Root classes get bonus method implementations if they don't have      // them already. These apply before category replacements.     // 如果根類還沒有額外的方法實現,那麼它們將獲得額外的方法。這些適用於類別替換之前。     if (cls->isRootMetaclass()) {         // root metaclass 根元類新增initialize初始化方法         addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);     }

// Attach categories. // 分類處理     if (previously) {         if (isMeta) {             objc::unattachedCategories.attachToClass(cls, previously, ATTACH_METACLASS);         } else {             // When a class relocates, categories with class methods             // may be registered on the class itself rather than on             // the metaclass. Tell attachToClass to look for those.             objc::unattachedCategories.attachToClass(cls, previously, ATTACH_CLASS_AND_METACLASS);         }     }     objc::unattachedCategories.attachToClass(cls, cls,                                              isMeta ? ATTACH_METACLASS : ATTACH_CLASS);

if DEBUG

// Debug: sanity-check all SELs; log method list contents    for (const auto& meth : rw->methods()) {         if (PrintConnecting) {             _objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-',                           cls->nameForLogging(), sel_getName(meth.name()));         }         ASSERT(sel_registerName(sel_getName(meth.name())) == meth.name());     }

endif

} ```

  • methodizeClass中主要就是對methodpropertyprotocol存放在rwe的處理,其實這裡的rwe並沒有值,因為還沒有完成初始化,
  • 為根元類新增initialize初始化方法,
  • 對分類處理。

上面是非懶載入類的處理,那麼懶載入類是在什麼時候完成初始化的呢?根據懶載入原則,應該就是在用的時候再去呼叫吧,那就驗證一下

原始碼環境中,我們在methodizeClass通過類名字加斷點

先在LGTeacher裡面實現+load方法,執行 image.png

這裡是從_objc_init_read_images進入的

LGTeacher裡面去掉+load方法,執行

image.png 這裡可以看到,整個流程是在main函式裡呼叫LGTeacheralloc方法來的,通過objc_msgSend訊息查詢流程的lookUpImpOrForward走到了這裡。

總結

類的載入通過類是否實現+load靜態例項方法,區分為懶載入類非懶載入類

  • 非懶載入類:是在啟動時map_images時載入進記憶體的,通過_getObjc2NonlazyClassList得到所有非懶載入類,迴圈呼叫realizeClassWithoutSwiftmethodizeClass完成初始化。

  • 懶載入類:是在第一次訊息傳送的時候,檢查類是否初始化,為完成初始化再去完成初始化流程。

關於訊息傳送文章:\ iOS底層之Runtime探索(一)\ iOS底層之Runtime探索(二)\ iOS底層之Runtime探索(三)

iOS 全網最新objc4 可調式/編譯原始碼
編譯好的原始碼的下載地址

以上是對iOS中類的載入通過原始碼的探索過程,如有疑問或錯誤之處請留言或私信我