iOS冷啟動優(yōu)化之模塊啟動項自注冊實現(xiàn)

背景

方案來自美團外賣冷啟動治理:http://www.reibang.com/p/8e0b38719278

  1. 在App啟動的時候功偿,如果將啟動項都寫在didFinishLaunch中媒吗,當啟動項非常多時,這一塊內(nèi)容會非常臃腫坝疼;
  2. 并不是所有的模塊啟動項都應該放在didFinishLaunch中搜贤,比如一個啟動項非常耗時,盡管可以寫在didFinishLaunch最后钝凶,但還是會影響首頁的渲染仪芒;而直接寫在首頁的viewDidAppear中,這些與首頁不相關的啟動項代碼會耦合在一起耕陷。
  3. 如果通過啟動階段發(fā)布通知掂名,模塊注冊響應通知來管理啟動項;那么模塊注冊通知的代碼需要寫在+load()函數(shù)中哟沫,這必然會影響冷啟動main()函數(shù)執(zhí)行之前階段饺蔑。

美團外賣[1]給出的思路就是在編譯時,將模塊的啟動函數(shù)指針保存在可執(zhí)行文件的__DATA段中嗜诀,在需要的執(zhí)行的時候從_DATA段中將函數(shù)指針取出來再執(zhí)行猾警。
先看一下實現(xiàn)效果,通過如下方式將模塊的啟動項注冊到STAGE_A階段啟動:

#import "XCDynamicLoader.h"

XC_FUNCTION_EXPORT(STAGE_A)(){
    // 啟動項代碼
}

加入STAGE_A步驟的啟動項需要在application:didFinishLaunchingWithOptions:中執(zhí)行隆敢,可以通過如下方式來實現(xiàn):

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // 執(zhí)行STAGE_A階段注冊的啟動項函數(shù)
    [XCDynamicLoader executeFunctionsForKey:@"STAGE_A"];
    return YES;
}

實現(xiàn)原理

實現(xiàn)原理就是在編譯時將數(shù)據(jù)(啟動項函數(shù)指針)保存進__DATA段发皿,在需要數(shù)據(jù)(啟動項函數(shù)指針)的時候從__DATA段中讀出來。如下圖[1]所示:

__DATA段數(shù)據(jù)讀寫

  1. 將數(shù)據(jù)寫入__DATA段
XC_FUNCTION_EXPORT(LEVEL_A)(){
    NSLog(@"level A, ViewController");
}

上述在模塊內(nèi)定義的啟動函數(shù)拂蝎,經(jīng)過預處理之后雳窟,展開結(jié)果如下所示:

// 啟動函數(shù)封裝在XC_Function結(jié)構(gòu)體中
struct XC_Function {
    char *key;
    void (*function)(void);
};

// 聲明啟動函數(shù)
static void _xcSTAGE_C(void);

// 將包含啟動函數(shù)的結(jié)構(gòu)體XC_Function保存在__DATA段的__STAGE_Cxc_func節(jié)中
 __attribute__((used, section("__DATA" ",__""STAGE_C" "xc_func"))) 
static const struct XC_Function __FSTAGE_C = (struct XC_Function){(char *)(&"STAGE_C"), (void *)(&_xcSTAGE_C)}; 

// 定義啟動函數(shù)
static void _xcSTAGE_C(){
    NSLog(@"STAGE C, TLMStageC, execute in viewDidAppear");
}

我們首先定義了啟動項函數(shù)void _xcSTAGE_C(),然后將啟動項函數(shù)指針存儲在struct XC_Function中,struct XC_Function還可以保存其他字段封救,然后將這個struct XC_Function寫入靜態(tài)變量__FSTAGE_C中。
最關鍵的地方是用于修飾靜態(tài)變量的“attribute((used, section("DATA" ",""STAGE_C" "xc_func"))) ”這一段代碼捣作,通過clang提供的section函數(shù)誉结,將struct XC_Function數(shù)據(jù)放置與__DATA段的"__STAGE_Cxcfunc"節(jié)中,如下圖所示:

__DATA段中自定義的__STAGE_Axc_func節(jié)

  1. 將數(shù)據(jù)從__DATA段中讀取出來
    從__DATA中讀取出來主要是通過“+[XCDynamicLoader executeFunctionsForKey:]”來指定具體的階段來讀取__DATA中相應的Section(節(jié))中保存的struct XC_Function券躁,然后取出其中的函數(shù)指針進行執(zhí)行惩坑。
    從MachO文件的Segment中讀取Section的具體方式如下所示:
NSArray<NSValue *>* XCReadSection(char *sectionName, const struct mach_header *mhp) {
    NSMutableArray *funcArray = [NSMutableArray array];
    
    const XCExportValue mach_header = (XCExportValue)mhp;
    const XCExportSection *section = XCGetSectByNameFromHeader((void *)mach_header, XCDYML_SEGMENTNAME, sectionName);
    if (section == NULL) return @[];
    
    int addrOffset = sizeof(struct XC_Function);
    for (XCExportValue addr = section->offset;
         addr < section->offset + section->size;
         addr += addrOffset) {
        
        struct XC_Function entry = *(struct XC_Function *)(mach_header + addr);
        [funcArray addObject:[NSValue valueWithPointer:entry.function]];
    }
    
    return funcArray;
}

XCReadSection函數(shù)的第一個參數(shù)是Section名字,即處于那一節(jié)也拜,第二個參數(shù)是MachO文件的mach_header以舒,讀取數(shù)據(jù)的段默認為__DATA。
在app中慢哈,可執(zhí)行文件是一個MachO文件蔓钟,動態(tài)庫也是一個MachO文件,這些MachO文件中都有可能注冊了啟動項卵贱,所以需要在app加載每一個MachO文件的時候都要讀取其中注冊的啟動項滥沫。我們使用_dyld_register_func_for_add_image函數(shù),該函數(shù)是用來注冊dyld加載鏡像時的回調(diào)函數(shù)键俱,在dyld加載鏡像時兰绣,會執(zhí)行注冊過的回調(diào)函數(shù)。

*_dyld_register_func_for_add_image()
registers the specified function to be called when a new image is added (a bundle or a dynamic shared library) to the program. When this function is first registered it is called for once for each image that is currently part of the process.

代碼如下所示:

__attribute__((constructor))
void initXCProphet() {
    _dyld_register_func_for_add_image(dyld_callback);
}

代碼中通過"attribute((constructor))"修飾了函數(shù)initXCProphet()编振,initXCProphet()會在可執(zhí)行文件(或動態(tài)庫)load的時候被調(diào)用缀辩,可以理解為在main()函數(shù)調(diào)用之前執(zhí)行。

我們在回調(diào)函數(shù)中踪央,讀取了每一個MachO文件中的注冊的各個階段的啟動函數(shù)臀玄,通過一個單例XCModuleManager保存起來:

static void dyld_callback(const struct mach_header *mhp, intptr_t vmaddr_slide) {
    for (NSString *stage in [XCModuleManager sharedManager].stageArray) {
        NSString *fKey = [NSString stringWithFormat:@"__%@%s", stage?:@"", XCDYML_SECTION_SUFFIX];
        NSArray *funcArray = XCReadSection((char *)[fKey UTF8String], mhp);
        [[XCModuleManager sharedManager] addModuleInitFuncs:funcArray forStage:stage];
    }
}

模塊啟動階段定義在了XCModuleManager中的stageArray中,模塊啟動項需要指定為其中一項來在指定階段來啟動:

- (instancetype)init {
    self = [super init];
    if (self) {        
        self.stageArray = @[
                            @"STAGE_A",
                            @"STAGE_B",
                            @"STAGE_C",
                            @"STAGE_D"
                            ];
        self.modInitFuncPtrArrayStageDic = [NSMutableDictionary dictionary];
        for (NSString *stage in self.stageArray) {
            self.modInitFuncPtrArrayStageDic[stage] = [NSMutableArray array];
        }
    }
    return self;
}

Next

上述功能是在__DATA中注冊模塊啟動函數(shù)杯瞻,同理__DATA中可以注冊字符串等其他數(shù)據(jù)镐牺,而美團外賣冷啟動中的例子"KLN_STRINGS_EXPORT("Key", "Value")"就是一個向__DATA中注冊字符串的案例,可以探索編譯時通過__DATA保存自定義數(shù)據(jù)的更多用途魁莉。

這是源碼地址:項目代碼

參考文獻

[1]:美團外賣冷啟動治理

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末睬涧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子旗唁,更是在濱河造成了極大的恐慌畦浓,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件检疫,死亡現(xiàn)場離奇詭異讶请,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進店門夺溢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來论巍,“玉大人,你說我怎么就攤上這事风响〖翁” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵状勤,是天一觀的道長鞋怀。 經(jīng)常有香客問我,道長持搜,這世上最難降的妖魔是什么密似? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮葫盼,結(jié)果婚禮上残腌,老公的妹妹穿的比我還像新娘。我一直安慰自己剪返,他們只是感情好废累,可當我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著脱盲,像睡著了一般邑滨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上钱反,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天掖看,我揣著相機與錄音,去河邊找鬼面哥。 笑死哎壳,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的尚卫。 我是一名探鬼主播归榕,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼吱涉!你這毒婦竟也來了刹泄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤怎爵,失蹤者是張志新(化名)和其女友劉穎特石,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鳖链,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡姆蘸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逞敷。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡狂秦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出推捐,到底是詐尸還是另有隱情故痊,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布玖姑,位于F島的核電站,受9級特大地震影響慨菱,放射性物質(zhì)發(fā)生泄漏焰络。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一符喝、第九天 我趴在偏房一處隱蔽的房頂上張望闪彼。 院中可真熱鬧,春花似錦协饲、人聲如沸畏腕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽描馅。三九已至,卻和暖如春而线,著一層夾襖步出監(jiān)牢的瞬間铭污,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工膀篮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留嘹狞,地道東北人。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓誓竿,卻偏偏與公主長得像磅网,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子筷屡,可洞房花燭夜當晚...
    茶點故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容