iOS啟動(dòng)優(yōu)化:App啟動(dòng)耗時(shí)在線監(jiān)控與AppDelegate管控

一、App啟動(dòng)耗時(shí)在線監(jiān)控

在大型的多團(tuán)隊(duì)合作的項(xiàng)目中,往往不經(jīng)意間的一個(gè)個(gè)改動(dòng),可能就會(huì)直接或累加式的拖慢App的啟動(dòng)速度,測(cè)試人員通過(guò)本地的錄屏或者開(kāi)發(fā)工具測(cè)量啟動(dòng)耗時(shí)由于受測(cè)試機(jī)器的狀態(tài)和樣本數(shù)量的原因數(shù)據(jù)往往有波動(dòng),并不能真正反饋App啟動(dòng)時(shí)間的真實(shí)變化。所以加入在線的數(shù)據(jù)監(jiān)控變得非常重要。

在介紹App啟動(dòng)耗時(shí)監(jiān)控之前,我們先大概回顧下App啟動(dòng)過(guò)程:

啟動(dòng)過(guò)程

main()函數(shù)之前的階段我們成為pre-main(),至于pre-main()中每個(gè)階段的具體作用,這邊就不再贅述,網(wǎng)上資料較多。

pre-main()開(kāi)始時(shí)間:__t1

蘋(píng)果并沒(méi)有直接提供App啟動(dòng)的開(kāi)始時(shí)間,目前業(yè)內(nèi)主要有兩種標(biāo)準(zhǔn)作為App的啟動(dòng)時(shí)間:

  • 第一個(gè)+(void)load被調(diào)用時(shí)

    我們知道+(void)load 方法調(diào)用發(fā)生 Initializer 階段, 根據(jù)動(dòng)態(tài)庫(kù)的加載順序調(diào)用+(void)load 方法,而動(dòng)態(tài)庫(kù)的加載順序是遞歸加載的,我們只要找到葉子節(jié)點(diǎn)的動(dòng)態(tài)庫(kù),然后在這個(gè)動(dòng)態(tài)庫(kù)中的添加+(void)load方法來(lái)記錄啟動(dòng)時(shí)間。很明顯,這種方式?jīng)]有統(tǒng)計(jì)到Initializer前面的時(shí)間,比如增加動(dòng)態(tài)庫(kù),Category等造成的啟動(dòng)耗時(shí)并不能被及時(shí)發(fā)現(xiàn)。

  • 獲取進(jìn)程創(chuàng)建時(shí)間

    我們的App實(shí)際上是一個(gè)進(jìn)程,如果能獲取到進(jìn)程的創(chuàng)建時(shí)間,也就是exec()階段的時(shí)間,更能提前記錄到App的啟動(dòng)開(kāi)始時(shí)間。

    
    #import <sys/sysctl.h>
    #import <mach/mach.h>
    
    + (CFAbsoluteTime)processStartTime {
        if (__t1 == 0) {
            struct kinfo_proc procInfo;
            int pid = [[NSProcessInfo processInfo] processIdentifier];
            int cmd[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
            size_t size = sizeof(procInfo);
            if (sysctl(cmd, sizeof(cmd)/sizeof(*cmd), &procInfo, &size, NULL, 0) == 0) {
                __t1 = procInfo.kp_proc.p_un.__p_starttime.tv_sec * 1000.0 + procInfo.kp_proc.p_un.__p_starttime.tv_usec / 1000.0;
            }
        }
        return __t1;
    }
    

pre-main()結(jié)束時(shí)間:__t2

獲取pre-main()結(jié)束時(shí)間相對(duì)容易,可以main()函數(shù)的開(kāi)始執(zhí)行時(shí)間,而我更推薦使用__attribute__((constructor)) 函數(shù)調(diào)用作為pre-main()的結(jié)束時(shí)間,這樣能最大程度的實(shí)現(xiàn)解耦:

void static __attribute__((constructor)) before_main() {
    if (__t2 == 0) {
        __t2 = CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970;
    }
} 

至于為什么不用最后一個(gè)load方法執(zhí)行時(shí)間?因?yàn)樵诖笮凸こ讨形覀儧](méi)辦法確定哪個(gè)load方法是最后一個(gè)...

啟動(dòng)完成時(shí)間:__t3

啟動(dòng)完成時(shí)間一般可以通過(guò)獲取didFinishLaunchingWithOptions:的結(jié)束時(shí)間,但是didFinishLaunchingWithOptions:的結(jié)束時(shí)間其實(shí)不包括啟動(dòng)圖動(dòng)畫(huà)的時(shí)間,啟動(dòng)圖動(dòng)畫(huà)執(zhí)行完成后的時(shí)間更接近用戶的感官。我們可以在執(zhí)行didFinishLaunchingWithOptions: 的runloop循環(huán)的后面的循環(huán)來(lái)獲取時(shí)間:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    //do somethings
    
    dispatch_async(dispatch_get_main_queue(), ^{
        if (__t3 == 0) {
            __t3 = CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970;
        }
    });
    return YES;
}

二、AppDelegate管控

在iOS項(xiàng)目中,AppDelegate類(lèi)里面的代碼往往都是雜亂無(wú)章,各模塊只要有需要,都會(huì)往AppDelegate中添加各種各樣的方法,特別是didFinishLaunchingWithOptions: 方法更是重災(zāi)區(qū)。為什么要把AppDelegate的管控和啟動(dòng)耗時(shí)監(jiān)控寫(xiě)在一起呢?因?yàn)?,合理且統(tǒng)一的模塊調(diào)用方式,可以更好的去統(tǒng)計(jì)每個(gè)模塊的調(diào)用耗時(shí),進(jìn)而實(shí)現(xiàn)更精細(xì)化的監(jiān)控。

  1. 抽象統(tǒng)一的方法;

    如需要在didFinishLaunchingWithOptions:被調(diào)用的模塊都在自己的模塊中實(shí)現(xiàn)+ (void)didFinishLaunchingWithOptions的靜態(tài)方法。

  2. didFinishLaunchingWithOptions:中使用rouer或者動(dòng)態(tài)調(diào)用各模塊的對(duì)應(yīng)的靜態(tài)方法。

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        
        //do somethings
        //...
        
        NSArray *modules = @[@"Module1",@"Module2",@"Module3",@"Module4",@"Module5"];
        for (NSString *module in modules) {
            //1.獲取開(kāi)始時(shí)間
            CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
            //2.調(diào)用模塊
            NSString *url = [NSString stringWithFormat:@"%@://didFinishLaunchingWithOptions",module];
            [Router openUrl:url];
            //3.獲取消耗的時(shí)間
            CFAbsoluteTime cost = CFAbsoluteTimeGetCurrent() - start;
            //4.記錄、上報(bào)
            ...
        }
        
        return YES;
    }
    
  3. 將AppDelegate制作成靜態(tài)庫(kù),如AppDelegate.framework。main函數(shù)中修改(非pods工程,記得在Other Linker Flags中添加-force_load ,避免鏈接時(shí)被優(yōu)化):

    int main(int argc, char * argv[]) {
     @autoreleasepool {
     return UIApplicationMain(argc, argv, nil, @"AppDelegate"));
     }
    }
    

AppDelegate這樣模塊可以交由專(zhuān)人維護(hù),進(jìn)而實(shí)現(xiàn)了AppDelegate的管控。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容