ios啟動優(yōu)化

夕陽最美時,也總是將近黃昏。
世上有很多事都是這樣子的,尤其是一些特別輝煌美好的事。
所以你不必傷感,也不用惋惜,縱然到江湖去趕上了春,也不必留住它。
因為這就是人生,有些事你留也留不住。
你一定要先學會忍受它的無情,才會懂得享受它的溫柔。

所以該靜心擼碼的時候,就不要想其他。好了,廢話少說,直接切入主題。

1. App啟動過程

解析Info.plist

  • 加載相關信息,例如如閃屏
  • 沙箱建立、權限檢查

Mach-O加載

  • 如果是胖二進制文件,尋找合適當前CPU類別的部分
  • 加載所有依賴的Mach-O文件(遞歸調用Mach-O加載的方法)
  • 定位內部、外部指針引用,例如字符串、函數等
  • 執(zhí)行聲明為attribute((constructor))的C函數
  • 加載類擴展(Category)中的方法
  • C++靜態(tài)對象加載、調用ObjC的 +load 函數

程序執(zhí)行

  • 調用main()
  • 調用UIApplicationMain()
  • 調用applicationWillFinishLaunching

什么是冷啟動、熱啟動

從電路角度來看:

熱啟動是在系統(tǒng)仍通電的情況下重新啟動系統(tǒng),熱啟動也是一次軟件復位。熱啟動清除易失性系統(tǒng)內存,并重新裝載操作系統(tǒng)。
冷啟動是用關閉電源來啟動系統(tǒng),冷啟動還對硬件進行復位,它檢查硬件,并重新裝載操作系統(tǒng)。
最重要的是冷啟動對硬件進行一次檢查?,F在的電腦這個過程好像不是很明顯,但是在40年前,這個硬件檢查一次可是很耗時間的。
冷啟動,電路會從斷開變成通路,期間,主機受到的影響類似我們開電燈的時候電燈受的影響(我說的是類似),大家都知道,電動設備啟動的時候會有一大電流沖擊。

從移動app角度來看:

當用戶按下home鍵的時候,iOS的App并不會馬上被kill掉,還會繼續(xù)存活若干時間。理想情況下,用戶點擊App的圖標再次回來的時候,App幾乎不需要做什么,就可以還原到退出前的狀態(tài),繼續(xù)為用戶服務。這種持續(xù)存活的情況下啟動App,我們稱為熱啟動,相對而言冷啟動就是App被kill掉以后一切從頭開始啟動的過程。我們這里只討論App冷啟動的情況。

image.png

測量Pre-main Time

在Xcode的菜單中選擇Project→Scheme→Edit Scheme...,然后找到 Run → Environment Variables
配置的 key 為:DYLD_PRINT_STATISTICS 設置1或者YES都可。

image.png

勾選如圖:


image.png

運行的時候會進行打?。?/p>

image.png

what`s the fuck?

main()函數之前總共使用了1.6ms
加載動態(tài)庫用了1.1ms,
指針重定位使用了392.11ms,

ObjC類初始化使用了46.43ms,
各種初始化使用了56.29ms。
在初始化耗費的56.29ms中,用時最多的初始化是libSystem.dylib、libMainThreadChecker.dylib

那么如何盡可能的減少pre-main花費的時間呢,主要就從輸出日志給出的四個階段下手:

  • 對動態(tài)庫加載的時間優(yōu)化
    每個App都進行動態(tài)庫加載,其中系統(tǒng)級別的動態(tài)庫占據了絕大數,而針對系統(tǒng)級別的動態(tài)庫都是經過系統(tǒng)高度優(yōu)化的,不用擔心時間的花費.開發(fā)者應該關注于自己集成到App的那些動態(tài)庫,這也是最能消耗加載時間的地方.對此Apple建議減少在App里開發(fā)者的動態(tài)庫集成或者有可能地將其多個動態(tài)庫最終集成一個動態(tài)庫后進行導入, 盡量保證將App現有的非系統(tǒng)級的動態(tài)庫個數保證在6個以內.

  • 減少Appp的Objective-C類,分類和的唯一Selector的個數
    這樣做主要是為了加快程序的整個動態(tài)鏈接, 在進行動態(tài)庫的重定位和綁定(Rebase/binding)過程中減少指針修正的使用,加快程序機器碼的生成.

  • 減少Objc運行初始化的時間花費.
    主要是類的注冊,分類的注冊,唯一選擇器的存在,以及涉及子父類內存布局的Non Fragile ivars偏移的更新,都會影響Objective-C運行時初始化的時間消耗.

  • 使用initialize方法進行必要的初始化工作.用+initialize方法替換調用原先在OC的+load方法中執(zhí)行初始代碼工作,從而加快所有類文件的加載速度.
    使用load不要在這里面執(zhí)行耗時的操作。其他的不是短時間能改變的。

測量main Time

一般項目的組織架構:


image.png

didFinishLaunchingWithOptions
didFinishLaunchingWithOptions這里面有的是必須執(zhí)行的,但是我們可以適當的根據功能的不同對應的適當延遲啟動的時機。對于我們項目,我將初始化分為三個類型:

  • 日志、統(tǒng)計等必須在 APP 一起動就最先配置的事件
  • 項目配置、環(huán)境配置、用戶信息的初始化 、推送、IM等事件
  • 其他 SDK 和配置事件

第一類,由于這類事件的特殊性,所以必須第一時間啟動,仍然把它留在 didFinishLaunchingWithOptions 里啟動。
第二類事件,這些功能在用戶進入 APP 主體的之前是必須要加載完的,所以我們可以把它放在第二批,也就是用戶已經看到廣告頁面,再進行廣告倒計時的時候再啟動。
第三類事件,由于不是必須的,所以我們可以放在第一個界面渲染完成以后的 viewDidAppear 方法里,這里完全不會影響到啟動時間。

參考文章,我們不妨提取出一個工具類,為了以后防止以后繼續(xù)優(yōu)化

下面是這個類的頭文件:

/**
 * 注意: 這個類負責所有的 didFinishLaunchingWithOptions 延遲事件的加載.
 * 以后引入第三方需要在 didFinishLaunchingWithOptions 里初始化或者我們自己的類需要在 didFinishLaunchingWithOptions 初始化的時候,
 * 要考慮盡量少的啟動時間帶來好的用戶體驗, 所以應該根據需要減少 didFinishLaunchingWithOptions 里耗時的操作.
 * 第一類: 比如日志 / 統(tǒng)計等需要第一時間啟動的, 仍然放在 didFinishLaunchingWithOptions 中.
 * 第二類: 比如用戶數據需要在廣告顯示完成以后使用, 所以需要伴隨廣告頁啟動, 只需要將啟動代碼放到 startupEventsOnADTimeWithAppDelegate 方法里.
 * 第三類: 比如直播和分享等業(yè)務, 肯定是用戶能看到真正的主界面以后才需要啟動, 所以推遲到主界面加載完成以后啟動, 只需要將代碼放到 startupEventsOnDidAppearAppContent 方法里.
 */

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface BLDelayStartupTool : NSObject

/**
 * 啟動伴隨 didFinishLaunchingWithOptions 啟動的事件.
 * 啟動類型為:日志 / 統(tǒng)計等需要第一時間啟動的.
 */
+ (void)startupEventsOnAppDidFinishLaunchingWithOptions;

/**
 * 啟動可以在展示廣告的時候初始化的事件.
 * 啟動類型為: 用戶數據需要在廣告顯示完成以后使用, 所以需要伴隨廣告頁啟動.
 */
+ (void)startupEventsOnADTime;

/**
 * 啟動在第一個界面顯示完(用戶已經進入主界面)以后可以加載的事件.
 * 啟動類型為: 比如直播和分享等業(yè)務, 肯定是用戶能看到真正的主界面以后才需要啟動, 所以推遲到主界面加載完成以后啟動.
 */
+ (void)startupEventsOnDidAppearAppContent;

@end

NS_ASSUME_NONNULL_END

下面是 .m 文件,這里做了一層自動校驗,如果 30 秒 以后,這些啟動項有沒有被啟動的,就會在 DEBUG 環(huán)境下彈出警告信息。同時也會將那些沒有啟動的啟動項進行啟動。

#import "BLDelayStartupTool.h"

static BOOL _isCalledStartupEventsOnAppDidFinishLaunchingWithOptions = NO;
static BOOL _isCalledStartupEventsOnADTimeWithAppDelegate = NO;
static BOOL _isCalledStartupEventsOnDidAppearAppContent = NO;
const NSTimeInterval kBLDelayStartupEventsToolCheckCallTimeInterval = 30;
@implementation BLDelayStartupTool

+ (void)load {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kBLDelayStartupEventsToolCheckCallTimeInterval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self checkStartupEventsDidLaunched];
    });
}

+ (void)checkStartupEventsDidLaunched {
    NSString *alertString = @"";
    if (!_isCalledStartupEventsOnAppDidFinishLaunchingWithOptions) {
        alertString = [alertString stringByAppendingString:@"AppDidFinishLaunching, "];
        [self startupEventsOnAppDidFinishLaunchingWithOptions];
    }
    if (!_isCalledStartupEventsOnADTimeWithAppDelegate) {
        alertString = [alertString stringByAppendingString:@"ADTime, "];
        [self startupEventsOnADTime];
    }
    if (!_isCalledStartupEventsOnDidAppearAppContent) {
        alertString = [alertString stringByAppendingString:@"DidAppearAppContent"];
        [self startupEventsOnDidAppearAppContent];
    }

    if (alertString.length > 0) {

#if DEBUG
        alertString = [alertString stringByAppendingString:@" 等延遲啟動項沒有啟動, 這會造成應用奔潰"];
        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"注意" message:alertString delegate:nil cancelButtonTitle:@"好的" otherButtonTitles:nil];
        [alertView show];
#endif
    }
}

+ (void)startupEventsOnAppDidFinishLaunchingWithOptions {
    _isCalledStartupEventsOnAppDidFinishLaunchingWithOptions = YES;
}

+ (void)startupEventsOnADTime {
    _isCalledStartupEventsOnADTimeWithAppDelegate = YES;
}

+ (void)startupEventsOnDidAppearAppContent {
    _isCalledStartupEventsOnDidAppearAppContent = YES;
}

@end

參考文章:
一次立竿見影的啟動時間優(yōu)化
WWDC 之優(yōu)化 App 啟動速度
App Startup Time: Past, Present, and Future
iOS App 啟動性能優(yōu)化
優(yōu)化 App 的啟動時間
iOS 啟動優(yōu)化 + 監(jiān)控實踐

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

相關閱讀更多精彩內容

友情鏈接更多精彩內容