總覽
- 利用已經(jīng)被內(nèi)核映射到內(nèi)存中的可執(zhí)行文件,
instantiateFromLoadedImage
生成 ImageLoader - 將依賴庫(kù)加載進(jìn)內(nèi)存,生成對(duì)應(yīng)的 ImageLoader(
loadInsertedDylib
) - 鏈接可執(zhí)行文件(
link
) - 鏈接依賴庫(kù)(
link
) - 調(diào)用所有 Image 的初始化方法 Initializers,包括動(dòng)態(tài)庫(kù)和可執(zhí)行文件,核心系統(tǒng)庫(kù)、objc自舉(
initializeMainExecutable
) - 返回程序入口函數(shù) main 的地址(
sMainExecutable->getMain()
)
簡(jiǎn)化過的代碼如下:
//
// Entry point for dyld. The kernel loads dyld and jumps to __dyld_start which
// sets up some registers and call this function.
//
// Returns address of main() in target program which __dyld_start jumps to
//
uintptr_t
_main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide,
int argc, const char* argv[], const char* envp[], const char* apple[],
uintptr_t* startGlue)
{
......
// 1. instantiate ImageLoader for main executable
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);
......
// 2. load any inserted libraries
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib) {
loadInsertedDylib(*lib);
}
}
......
// 3. link main executable
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL));
......
// 4. link any inserted libraries
// do this after linking main executable so that any dylibs pulled in by inserted
// dylibs (e.g. libSystem) will not be in front of dylibs the program uses
if ( sInsertedDylibCount > 0 ) {
for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
ImageLoader* image = sAllImages[i+1];
link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL));
image->setNeverUnloadRecursive();
}
}
// 5. run all initializers
initializeMainExecutable();
// 6. main executable uses LC_UNIXTHREAD, dyld needs to let "start" in program set up for main()
result = (uintptr_t)sMainExecutable->getMain();
return result;
}
可執(zhí)行文件和依賴庫(kù)的鏈接
void ImageLoader::link(const LinkContext& context, bool forceLazysBound, bool preflightOnly, bool neverUnload, const RPathChain& loaderRPaths)
{
// 遞歸加載
this->recursiveLoadLibraries(context, preflightOnly, loaderRPaths);
// 遞歸Rebase 修復(fù) ASLR 造成的地址錯(cuò)位的問題,增加一個(gè)偏移量
// 主要是 IO 操作
this->recursiveRebase(context);
// 遞歸符號(hào)綁定,將指針指向 image 外部的內(nèi)容。需要查詢符號(hào)表,性能消耗主要是 CPU 計(jì)算
this->recursiveBind(context, forceLazysBound, neverUnload);
}
Initializers
void initializeMainExecutable()
{
......
// 首先執(zhí)行動(dòng)態(tài)庫(kù)的 initialzers
// run initialzers for any inserted dylibs
ImageLoader::InitializerTimingList initializerTimes[sAllImages.size()];
initializerTimes[0].count = 0;
const size_t rootCount = sImageRoots.size();
if ( rootCount > 1 ) {
for(size_t i=1; i < rootCount; ++i) {
sImageRoots[i]->runInitializers(gLinkContext, initializerTimes[0]);
}
}
// 隨后執(zhí)行可執(zhí)行文件的 initialzers
// run initializers for main executable and everything it brings up
sMainExecutable->runInitializers(gLinkContext, initializerTimes[0]);
// 進(jìn)行終止化
(*gLibSystemHelpers->cxa_atexit)(&runAllStaticTerminators, NULL, NULL);
// 如果設(shè)置了環(huán)境變量 DYLD_PRINT_STATISTICS,可以在 Xcode debug 的時(shí)候在控制臺(tái)打印
// dump info if requested
if ( sEnv.DYLD_PRINT_STATISTICS )
ImageLoaderMachO::printStatistics((unsigned int)sAllImages.size(), initializerTimes[0]);
}
這里的 initialzers
注意不是 Objective-C 中的 initialzers 方法,而是 C++靜態(tài)對(duì)象初始化構(gòu)造器。
在 Xcode 中,可以通過設(shè)置環(huán)境變量 DYLD_PRINT_STATISTICS
打印所有 initialzers 方法。
運(yùn)行 App 后,可以看到控制臺(tái)打印
通過打印可以發(fā)現(xiàn),其中排第一個(gè)的是 libSystem.B.dylib,排在最后的是可執(zhí)行文件的方法。
在 libSystem.B.dylib 的 initialzers 函數(shù)里的 libdispatch_init 調(diào)用到了 Runtime 初始化方法 _objc_init。
通過在 Xcode 中設(shè)置 _objc_init 符號(hào)斷點(diǎn),可以看到在控制臺(tái)打印 dyld: calling initializer function 0x1ba93e7c0 in /usr/lib/libSystem.B.dylib
后, _objc_init 被調(diào)用了,不過調(diào)用棧將中間的調(diào)用過程全部隱去了。
objc 源碼部分
查看 libdispatch源碼,可以發(fā)現(xiàn) objc 的蹤跡。
libdispatch_init -> _os_object_init -> _objc_init
void
libdispatch_init(void)
{
......
_os_object_init();
.....
}
void
_os_object_init(void)
{
_objc_init();
......
}
這下終于來到了熟悉的 objc4 的源碼,這里我們重點(diǎn)關(guān)注在 dyld 中注冊(cè)了的三個(gè)回調(diào)。
三個(gè)回調(diào)時(shí)機(jī)依次是 objc image 的 mapped、initialized、unmapped 三個(gè)階段
void _objc_init(void)
{
// so many init
......
// 在 dyld 中注冊(cè)了三個(gè)回調(diào)
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
map_images
當(dāng) dyld 完成映射(mapped)后,開始執(zhí)行。其中核心函數(shù)是 _read_images
。
完成的工作主要是:
- 從庫(kù)中對(duì)應(yīng)的 segment 讀取 class、protocol、category 信息,載入到 Runtime。
- 將 class、protocol、category 信息向 Runtime 注冊(cè)結(jié)構(gòu)
- 將 category 需要加到對(duì)應(yīng)的 class 上。
- realized 類(重新確定布局,在 class_ro_t 基礎(chǔ)上創(chuàng)建 class_rw_t)
其中需要說明的是 class 中 class_ro_t 為 unrealized class,class_rw_t 為 realized class。
如果想要使用 class,必須是 class_rw_t。class_ro_t 經(jīng)過 resolve 后,會(huì)轉(zhuǎn)化為 class_rw_t。non-lazy class 要求在初始化進(jìn)行 realize。
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
// 注冊(cè) selector 到一個(gè)全局表中
for(......) {
SEL sel = sel_registerNameNoLock(name, isBundle);
}
// 獲取 classes,并 realize
classref_t const *classlist = _getObjc2ClassList(hi, &count);
......
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
// protocol
protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
for (i = 0; i < count; i++) {
// 注冊(cè)結(jié)構(gòu)
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
// categories
for() {
// 在 class 中注冊(cè) category,
// 如果 class 已經(jīng) realize,重建 class
attachCategories(cls, &lc, 1, ATTACH_EXISTING);
}
// Realize non-lazy classes (for +load methods and static instances)
classref_t const *classlist =
_getObjc2NonlazyClassList(hi, &count);
for(......) {
// 將類添加到 table 中
addClassTableEntry(cls);
realizeClassWithoutSwift(cls, nil);
}
// Realize newly-resolved future classes
for(......) {
realizeClassWithoutSwift(cls, nil);
}
}
realizeClass 干的事情包括:
- 遞歸 Realize 父類和元類
- 重新設(shè)置 class、superclass、metaclass 之間的關(guān)系
- 重新計(jì)算 instance variable layout
- 將 class 中的編譯時(shí)已經(jīng)確定的 class_ro_t 的內(nèi)容(method、property、protocol)放到 class_rw_t 結(jié)構(gòu)體上,并增加 category 的部分
static Class realizeClass(Class cls)
{
// realize 父類和元類
supercls = realizeClass(remapClass(cls->superclass));
metacls = realizeClass(remapClass(cls->ISA()));
// 重新設(shè)置關(guān)系
cls->superclass = supercls;
cls->initClassIsa(metacls);
// 重新計(jì)算 layout
// 如果 superclass 和原 class 空間重疊,需要對(duì)原 class 的實(shí)例重新計(jì)算位置并調(diào)整
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);
// Connect this class to its superclass's subclass lists
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
// Attach categories
methodizeClass(cls);
}
methodizeClass 函數(shù)干的事包括:
- 將 class 中的 class_ro_t 的內(nèi)容(method、property、protocol)放到 class_rw_t 結(jié)構(gòu)體上,
- 增加 category 的部分
static void methodizeClass(Class cls)
{
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro;
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
// 方法增加
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
rw->methods.attachLists(&list, 1);
}
property_list_t *proplist = ro->baseProperties;
// 屬性增加
if (proplist) {
rw->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
// 協(xié)議增加
rw->protocols.attachLists(&protolist, 1);
}
// Attach categories,將 method、property、protocol 插入
category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
attachCategories(cls, cats, false /*don't flush caches*/);
}
load_images
是 dyld 的第二個(gè)注冊(cè)回調(diào),其核心是調(diào)用 load 方法。
- 查找并保存所有類,主類存在
loadable_classes
中,其排列順序是父類在前、子類在后,分類存在loadable_categories
中。 - 先調(diào)用
loadable_classes
中類的 load 方法,然后調(diào)用loadable_categories
中分類的 load 方法
void
load_images(const char *path __unused, const struct mach_header *mh)
{
prepare_load_methods((const headerType *)mh);
call_load_methods();
}
void call_load_methods(void)
{
......
void *pool = objc_autoreleasePoolPush();
do {
// 1. 首先調(diào)用類的 load
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. 調(diào)用分類的 load
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);
......
}
// 調(diào)用 load 方法,并沒有走消息發(fā)送機(jī)制,而是直接進(jìn)行調(diào)用
(*load_method)(cls, @selector(load));
啟動(dòng)時(shí)間優(yōu)化
目前為止是 App 啟動(dòng)時(shí),pre-main 部分,也就是還沒有走到 App 入口的 main 函數(shù)。
load dylibs
iOS App 一般需要加載 100~400 個(gè) dylibs。其中包括了系統(tǒng)和開發(fā)者引入的。加載 dylibs 會(huì)消耗 App 的啟動(dòng)時(shí)間。可以做優(yōu)化的部分是自己引入的 dylib。Apple 在 WWDC 上建議,建議盡量將第三方 dylibs 個(gè)數(shù)控制在 6 個(gè)以內(nèi)。
優(yōu)化方案:
- 使用靜態(tài)庫(kù)替代動(dòng)態(tài)庫(kù),如果使用 Cocoapods 管理第三方庫(kù)的話,可以將 podfile 中的
use_frameworks!
注釋掉,然后pod install
來將動(dòng)態(tài)庫(kù)變更為靜態(tài)庫(kù)。 - 合并動(dòng)態(tài)庫(kù),減少動(dòng)態(tài)庫(kù)的數(shù)量,這一塊筆者沒有進(jìn)行嘗試。
Rebase/Binding
這兩步主要是對(duì) image 內(nèi)部指針的修復(fù)。因此只要指針數(shù)量越少,修復(fù)的耗時(shí)就會(huì)變少,其中的關(guān)鍵是減少 _DATA 段中指針的數(shù)量。
優(yōu)化方案:
- 減少類、方法的數(shù)量,比如刪除廢棄的類、方法
- 使用 Swift 的 struct(WWDC 介紹結(jié)構(gòu)內(nèi)部有做優(yōu)化,指針數(shù)量少)
- 減少 C++ 虛函數(shù)的數(shù)量(這塊可優(yōu)化的空間較小)