夕陽最美時,也總是將近黃昏。
世上有很多事都是這樣子的,尤其是一些特別輝煌美好的事。
所以你不必傷感,也不用惋惜,縱然到江湖去趕上了春,也不必留住它。
因為這就是人生,有些事你留也留不住。
你一定要先學會忍受它的無情,才會懂得享受它的溫柔。
所以該靜心擼碼的時候,就不要想其他。好了,廢話少說,直接切入主題。
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冷啟動的情況。

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

勾選如圖:

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

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
一般項目的組織架構:

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)控實踐