1. 程式人生 > 實用技巧 >iOS 底層原理之—dyld 與 objc 的關聯

iOS 底層原理之—dyld 與 objc 的關聯

前言

dyld載入過程中,我們知道會呼叫_objc_init方法,那麼在_objc_init方法中究竟做了什麼呢?我們來探究下。

_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); #if __OBJC2__ didCallDyldNotifyRegister = true; #endif }

_objc_init實現中我們分析下該方法主要做了什麼

environ_init()

該方法主要是讀取執行時的環境變數,我們可以通過設定DYLD_PRINT_STATISTICS = YES

來列印APP啟動到main()函式之前的時長,進而可以進行APP啟動優化。具體的environ_init()簡介可參考部落格iOS-底層原理 16:dyld與objc的關聯中有關nviron_init()部分的介紹

tls_init()

主要用於關於執行緒key的繫結,比如每執行緒資料的解構函式。

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++靜態建構函式

static void static_init() { size_t count; auto inits = getLibobjcInitializers(&_mh_dylib_header, &count); for (size_t i = 0; i < count; i++) { inits[i](); } }

runtime_init()

主要是執行時的初始化,主要分為兩部分:分類初始化類的表初始化

void runtime_init(void) { objc::unattachedCategories.init(32); objc::allocatedClasses.init(); } 複製程式碼

exception_init()

初始化libobjc異常處理

/*********************************************************************** * exception_init * Initialize libobjc's exception handling system. * Called by map_images(). **********************************************************************/ void exception_init(void) { old_terminate = std::set_terminate(&_objc_terminate); }

cache_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++; } 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 }

_imp_implementationWithBlock_init()

主要用來啟動機制回撥

/// everything is initialized lazily, but for certain processes we eagerly load /// the 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. // // 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關聯

_dyld_objc_notify_register(&map_images, load_images, unmap_image)

主要是dyld註冊 實際程式碼實現

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); }

從上文正中我們可以看出

  • mappedmap_images

  • initload_images

  • unmappedunmap_image

map_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函式中我們發現map_images_nolock函式是重點,我們進入map_images_nolock函式

map_images_nolock

我們檢視程式碼實現

從截圖中我們可以看出_read_images是我們要重點研究的方法

_read_images函式分析

是否是第一次載入

修復預編譯時@selector的錯亂問題

錯誤類處理,通過readClass讀取出來類的資訊

重新設定對映映象

訊息處理

類中如果有協議,讀取協議

對映協議

載入分類

注意在分類處理中主要是通過load_categories_nolock處理,我們進入load_categories_nolock函式中

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 { objc::unattachedCategories.addForClass(lc, cls->ISA()); } } } } }; processCatlist(_getObjc2CategoryList(hi, &count)); processCatlist(_getObjc2CategoryList2(hi, &count)); }

load_categories_nolock函式實現中,我們可以看到該函式將例項方法協議屬性類方法等再次連結了一次。

非懶載入類處理

處理沒有使用的類

dyld與objc關聯總結

  • dyld_start呼叫_objc_init來初始化,_objc_init中通過dyld呼叫_dyld_objc_notify_register函式,傳入map_imagesload_images這兩個引數來處理

  • map_images通過map_images_nolock函式呼叫_read_images函式

  • _read_images函式中處理類資訊、屬性、協議、分類等

  • 當一切準備妥當,則再次返回dyld_start中,此時dyldobjc關聯了起來

資料推薦

如果你正在跳槽或者正準備跳槽不妨動動小手,新增一下咱們的交流群1012951431來獲取一份詳細的大廠面試資料為你的跳槽多添一份保障。