當(dāng) App 啟動(dòng)的時(shí)候,系統(tǒng)首先會(huì)加載 APP 的可執(zhí)行文件,然后獲得 dyld 所在路徑,加載 dyld,接著后面的事情就交給 dyld 了。dylb 是什么呢?
Dylb
全稱(chēng) the dynamic link editor,動(dòng)態(tài)鏈接器, 源碼在這里 。
image 鏡像
可以理解為程序中對(duì)應(yīng)實(shí)例,可以是可執(zhí)行文件、Framework、dylib、bundle 文件。一個(gè) App 包含很多鏡像,比如:Foundation、 CoreServices 等等。
ImageLoader
用來(lái)加載 image 鏡像的工具。
Mach-O
全稱(chēng)為 Mach Object,是 MAC 下可執(zhí)行文件格式,類(lèi)似 windows exe 文件,主要包括以下三種類(lèi)型。
-
executable程序可執(zhí)行文件。 -
dylib動(dòng)態(tài)鏈接庫(kù),類(lèi)似windows dll、linux so。 -
bundle資源文件,使用dlopen加載。
引用 wwdc2016/406 :
File Types:
Executable—Main binary for application
Dylib—Dynamic library (aka DSO or DLL)
Bundle—Dylib that cannot be linked, only dlopen(), e.g. plug-ins
Dylb Setup
我們回到 App 啟動(dòng),在系統(tǒng)內(nèi)核做好程序準(zhǔn)備工作之后,交由 dyld 負(fù)責(zé)剩下的工作,我們看下 dyld 做了哪些事情。

以上摘自 wwdc2016/406 pdf 第59頁(yè)。
load dylibs
Map all dependent dylibs, recurse,遞歸找到所有依賴(lài)的 dylibs(動(dòng)態(tài)庫(kù))
rebase
Rebase all images,調(diào)整所有鏡像內(nèi)的指針,添加一個(gè) slide 偏差值,對(duì)于 slide 的解釋?zhuān)?code>WWDC 是這樣說(shuō)的:
Slide = actual_address - preferred_address
可能是出于安全考慮,引入了 ASLR,全稱(chēng) Address Space Layout Randomization。
ASLR
.Address Space Layout Randomization
.Images load at random address
大概意思就是鏡像會(huì)加載在隨機(jī)的地址上,和 actual_address 會(huì)有一個(gè)偏差(slide),dyld 需要修正這個(gè)偏差,來(lái)指向正確的地址。
bind
Bind all images,查詢(xún)符號(hào)表,設(shè)置指向鏡像外部的指針。
objc prepare images
objc prepare images,通知 runtime 準(zhǔn)備鏡像,這里做的事情比較多,主要是 runtime 的初始化,引用 WWDC 解釋?zhuān)?/p>
.Most ObjC set up done via rebasing and binding
.All ObjC class definitions are registered
.Non-fragile ivars offsets updated
.Categories are inserted into method lists
.Selectors are uniqued
大部分 ObjC 的初始化工作已經(jīng)完成,接下來(lái)注冊(cè)所有的 objc class,更新 ivars 偏移(runtime 2.0 新特性,二進(jìn)制兼容),把分類(lèi)的方法插入到方法列表里,再檢查 selector 唯一性,具體實(shí)現(xiàn)可以看 map_images 。
initializers
到了這一步,dylib 開(kāi)始調(diào)用 C++ 靜態(tài)構(gòu)造函數(shù),然后調(diào)用 class load(父類(lèi)優(yōu)先調(diào)用),再調(diào)用 category load,接著調(diào)用 __attribute__((constructor) 的構(gòu)造函數(shù)。最后調(diào)用 main 。
.C++ generates initializer for statically allocated objects
.ObjC +load methods
.Run "bottom up" so each initializer can call dylibs below it
.Lastly, Dyld calls main() in executable
到這里,整個(gè)啟動(dòng)流程基本就結(jié)束了。
_objc_init 補(bǔ)充
在啟動(dòng)初始化的時(shí)候,會(huì)調(diào)用 _objc_init,這里做一些準(zhǔn)備工作,比如說(shuō)加載環(huán)境變量、初始化靜態(tài)構(gòu)造函數(shù),注冊(cè) 鏡像映射、鏡像加載、鏡像卸載回調(diào)等等。我們打開(kāi) objc-os.mm,找到以下代碼:
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/
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();//環(huán)境變量
tls_init();
static_init();//靜態(tài)構(gòu)造函數(shù)
lock_init();
exception_init();
//注冊(cè) images 回調(diào)
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
environ_init
讀取環(huán)境配置方法,讀取在 Xcode 中配置的環(huán)境變量參數(shù)。具體可以看 objc-env.h 文件。
OPTION( PrintImages, OBJC_PRINT_IMAGES, "log image and library names as they are loaded")
OPTION( PrintImageTimes, OBJC_PRINT_IMAGE_TIMES, "measure duration of image loading steps")
OPTION( PrintLoading, OBJC_PRINT_LOAD_METHODS, "log calls to class and category +load methods")
OPTION( PrintInitializing, OBJC_PRINT_INITIALIZE_METHODS, "log calls to class +initialize methods")
//省略...
tls_init
初始化線(xiàn)程的析構(gòu)函數(shù),具體可以看 objc-runtime.mm 文件。
void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
_objc_pthread_key = TLS_DIRECT_KEY;
pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
_objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}
static_init
調(diào)用 C++的靜態(tài)構(gòu)造函數(shù),具體可以看 objc-os.mm 文件。
/***********************************************************************
* static_init
* Run C++ static constructor functions.
* libc calls _objc_init() before dyld would call our static constructors,
* so we have to do it ourselves.
**********************************************************************/
static void static_init()
{
size_t count;
Initializer *inits = getLibobjcInitializers(&_mh_dylib_header, &count);
for (size_t i = 0; i < count; i++) {
inits[i]();
}
}
lock_init
初始化鎖,具體可以看 objc-runtime-new.mm 文件。
void lock_init(void)
{
#if SUPPORT_QOS_HACK
BackgroundPriority = _pthread_qos_class_encode(QOS_CLASS_BACKGROUND, 0, 0);
MainPriority = _pthread_qos_class_encode(qos_class_main(), 0, 0);
# if DEBUG
pthread_key_init_np(QOS_KEY, &destroyQOSKey);
# endif
#endif
}
exception_init
初始化 exception handle,具體可以看 objc-exception.mm 文件。
/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system.
* Called by map_images().
**********************************************************************/
void exception_init(void)
{
old_terminate = std::set_terminate(&_objc_terminate);
}
_dyld_objc_notify_register
注冊(cè) 準(zhǔn)備鏡像、加載鏡像、卸載鏡像的回調(diào),每當(dāng)有新的鏡像被加載的時(shí)候,都會(huì)調(diào)用這些回調(diào)。
map_images
當(dāng) runtime 收到 dylb 的準(zhǔn)備鏡像通知 的時(shí)候,開(kāi)始初始化 runtime,注冊(cè) objc class,更新 ivars offset,把 category 方法合到主類(lèi)等等,打開(kāi) objc-runtime-new.mm,找到以下代碼:
/***********************************************************************
* 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[])
{
rwlock_writer_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
進(jìn)入方法,先加鎖,這里使用了讀寫(xiě)鎖,然后交給 map_images_nolock 處理。
map_images_nolock
準(zhǔn)備鏡像具體實(shí)現(xiàn),實(shí)現(xiàn)共享內(nèi)存優(yōu)化,默認(rèn)方法注冊(cè)、自動(dòng)釋放池和散列表初始化及類(lèi)的加載等等操作。方法比較長(zhǎng),截取了部分:
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
static bool firstTime = YES;
header_info *hList[mhCount];
uint32_t hCount;
size_t selrefCount = 0;
// Perform first-time initialization if necessary.
// This function is called before ordinary library initializers.
// fixme defer initialization until an objc-using image is found?
if (firstTime) {
//預(yù)優(yōu)化初始化
preopt_init();
}
//統(tǒng)計(jì)class 數(shù)量
// Find all images with Objective-C metadata.
hCount = 0;
// Count classes. Size various table based on the total.
int totalClasses = 0;
// Perform one-time runtime initialization that must be deferred until
// the executable itself is found. This needs to be done before
// further initialization.
// (The executable may not be present in this infoList if the
// executable does not contain Objective-C code but Objective-C
// is dynamically loaded later.
if (firstTime) {
sel_init(selrefCount);
// 自動(dòng)釋放池和散列表初始化
arr_init();
//讀取鏡像
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
firstTime = NO;
}
//自動(dòng)釋放池、散列表初始化
void arr_init(void)
{
AutoreleasePoolPage::init();
SideTableInit();
}
最后讀取鏡像,調(diào)用 _read_images。
_read_images
讀取鏡像,方法內(nèi)做了很多事情,加載類(lèi)、注冊(cè)方法、加載虛函數(shù)表、加載協(xié)議 Protocol 和非延遲類(lèi)方法、加載靜態(tài)實(shí)例、加載分類(lèi)。代碼太多,截取了部分,具體可以看 objc-runtime-new.mm。
/***********************************************************************
* _read_images
* Perform initial processing of the headers in the linked
* list beginning with headerList.
*
* Called by: map_images_nolock
*
* Locking: runtimeLock acquired by map_images
**********************************************************************/
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
header_info *hi;
uint32_t hIndex;
size_t count;
size_t i;
Class *resolvedFutureClasses = nil;
//加載類(lèi)
for (EACH_HEADER) {
if (! mustReadClasses(hi)) {
// Image is sufficiently optimized that we need not call readClass()
continue;
}
bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->isPreoptimized();
classref_t *classlist = _getObjc2ClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = (Class)classlist[i];
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;
}
}
}
//注冊(cè)方法
// Fix up @selector references
static size_t UnfixedSelectors;
sel_lock();
for (EACH_HEADER) {
if (hi->isPreoptimized()) continue;
bool isBundle = hi->isBundle();
SEL *sels = _getObjc2SelectorRefs(hi, &count);
UnfixedSelectors += count;
for (i = 0; i < count; i++) {
const char *name = sel_cname(sels[i]);
sels[i] = sel_registerNameNoLock(name, isBundle);
}
}
sel_unlock();
//加載虛函數(shù)表
#if SUPPORT_FIXUP
// Fix up old objc_msgSend_fixup call sites
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++) {
fixupMessageRef(refs+i);
}
}
#endif
//加載協(xié)議
// 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->isPreoptimized();
bool isBundle = hi->isBundle();
protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
for (i = 0; i < count; i++) {
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}
// Fix up @protocol references
// Preoptimized images may have the right
// answer already but we don't know for sure.
for (EACH_HEADER) {
protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
for (i = 0; i < count; i++) {
remapProtocolRef(&protolist[i]);
}
}
//重新布局class
// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) {
classref_t *classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
// hack for class __ARCLite__, which didn't get this above
#if TARGET_OS_SIMULATOR
if (cls->cache._buckets == (void*)&_objc_empty_cache &&
(cls->cache._mask || cls->cache._occupied))
{
cls->cache._mask = 0;
cls->cache._occupied = 0;
}
if (cls->ISA()->cache._buckets == (void*)&_objc_empty_cache &&
(cls->ISA()->cache._mask || cls->ISA()->cache._occupied))
{
cls->ISA()->cache._mask = 0;
cls->ISA()->cache._occupied = 0;
}
#endif
realizeClass(cls);
}
}
//加載分類(lèi) category
// Discover categories.
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Disavow any knowledge of this category.
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}
}
到此鏡像映射完成。
load_images
當(dāng) runtime 收到 dylb 的加載鏡像通知 的時(shí)候,會(huì)調(diào)用這個(gè)方法,作用是加載鏡像,打開(kāi) objc-runtime-new.mm,找到以下代碼:
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
rwlock_writer_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
不難發(fā)現(xiàn) load_images 主要做了兩件事,先調(diào)用 prepare_load_methods 進(jìn)行 load 準(zhǔn)備,接著調(diào)用 call_load_methods 執(zhí)行所有的 load 方法。
prepare_load_methods
//load 預(yù)處理
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertWriting();
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
//這里先處理所有class load
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
//再處理 category load
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
realizeClass(cls);
assert(cls->ISA()->isRealized());
//添加到 loadable_categories 全局結(jié)構(gòu)體里
add_category_to_loadable_list(cat);
}
}
不難發(fā)現(xiàn),先調(diào)用 schedule_class_load 處理 class load,然后再處理 category load。那么處理 class load,父類(lèi)和子類(lèi)是什么順序呢?我們繼續(xù)看代碼:
//處理 class load,superclass 的在前面
static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
//這里先添加 superClass 的 load
// Ensure superclass-first ordering
schedule_class_load(cls->superclass);
//再添加 class load
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
//處理 category load
void add_category_to_loadable_list(Category cat)
{
IMP method;
loadMethodLock.assertLocked();
method = _category_getLoadMethod(cat);
if (loadable_categories_used == loadable_categories_allocated) {
loadable_categories_allocated = loadable_categories_allocated*2 + 16;
loadable_categories = (struct loadable_category *)
realloc(loadable_categories,
loadable_categories_allocated *
sizeof(struct loadable_category));
}
loadable_categories[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
loadable_categories_used++;
}
schedule_class_load 內(nèi)部寫(xiě)的很清楚,優(yōu)先存儲(chǔ) superclass load,然后再調(diào)用 add_class_to_loadable_list 存儲(chǔ)自身的 class load。到這里 load 準(zhǔn)備工作做完了。
call_load_methods
接下來(lái)開(kāi)始調(diào)用所有的 load,我們先看源碼:
//調(diào)用所有的 load 方法
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1\. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
//循環(huán)調(diào)用 class 的 load 方法,直到完成所有調(diào)用為止
call_class_loads();
}
// 調(diào)用分類(lèi)的 load 方法
// 2\. Call category +loads ONCE
more_categories = call_category_loads();
// 3\. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
很明顯,這里先用一個(gè) while 循環(huán) 執(zhí)行 class load,然后再調(diào)用 category load。
注意
-
class load和category load不是通過(guò)方法查找調(diào)用load方法,而是通過(guò)方法列表去到load方法的IMP方法地址直接調(diào)用。所以類(lèi)的和分類(lèi)的load方法 都會(huì)調(diào)用。
總結(jié)
最后,最后我們總結(jié)一下:
- 準(zhǔn)備
load方法的時(shí)候,先處理superclass load,再處理子類(lèi);最后準(zhǔn)備category load,單獨(dú)存儲(chǔ)。
- 調(diào)用類(lèi)的
load方法。- 先編譯先調(diào)用。
- 先調(diào)用父類(lèi)的
load方法,再調(diào)用子類(lèi)的load方法。
- 調(diào)用Category的
load方法。- 先編譯先調(diào)用。
-
class load和category load不是通過(guò)方法查找調(diào)用load方法,而是通過(guò)方法列表去到load方法的IMP方法地址直接調(diào)用。所以類(lèi)的和分類(lèi)的load方法 都會(huì)調(diào)用。 - 一個(gè)類(lèi)的只調(diào)用
load方法一次。