夕陽最美時本谜,也總是將近黃昏初家。
世上有很多事都是這樣子的,尤其是一些特別輝煌美好的事乌助。
所以你不必傷感溜在,也不用惋惜,縱然到江湖去趕上了春他托,也不必留住它掖肋。
因為這就是人生,有些事你留也留不住赏参。
你一定要先學(xué)會忍受它的無情志笼,才會懂得享受它的溫柔。
所以該靜心擼碼的時候把篓,就不要想其他纫溃。好了,廢話少說纸俭,直接切入主題皇耗。
1. App啟動過程
解析Info.plist
- 加載相關(guān)信息南窗,例如如閃屏
- 沙箱建立揍很、權(quán)限檢查
Mach-O加載
- 如果是胖二進制文件,尋找合適當前CPU類別的部分
- 加載所有依賴的Mach-O文件(遞歸調(diào)用Mach-O加載的方法)
- 定位內(nèi)部万伤、外部指針引用窒悔,例如字符串、函數(shù)等
- 執(zhí)行聲明為attribute((constructor))的C函數(shù)
- 加載類擴展(Category)中的方法
- C++靜態(tài)對象加載敌买、調(diào)用ObjC的 +load 函數(shù)
程序執(zhí)行
- 調(diào)用main()
- 調(diào)用UIApplicationMain()
- 調(diào)用applicationWillFinishLaunching
什么是冷啟動简珠、熱啟動
從電路角度來看:
熱啟動是在系統(tǒng)仍通電的情況下重新啟動系統(tǒng),熱啟動也是一次軟件復(fù)位。熱啟動清除易失性系統(tǒng)內(nèi)存聋庵,并重新裝載操作系統(tǒng)膘融。
冷啟動是用關(guān)閉電源來啟動系統(tǒng),冷啟動還對硬件進行復(fù)位祭玉,它檢查硬件氧映,并重新裝載操作系統(tǒng)。
最重要的是冷啟動對硬件進行一次檢查⊥鸦酰現(xiàn)在的電腦這個過程好像不是很明顯岛都,但是在40年前,這個硬件檢查一次可是很耗時間的振峻。
冷啟動臼疫,電路會從斷開變成通路,期間扣孟,主機受到的影響類似我們開電燈的時候電燈受的影響(我說的是類似)烫堤,大家都知道,電動設(shè)備啟動的時候會有一大電流沖擊凤价。
從移動app角度來看:
當用戶按下home鍵的時候塔逃,iOS的App并不會馬上被kill掉,還會繼續(xù)存活若干時間料仗。理想情況下湾盗,用戶點擊App的圖標再次回來的時候,App幾乎不需要做什么立轧,就可以還原到退出前的狀態(tài)格粪,繼續(xù)為用戶服務(wù)。這種持續(xù)存活的情況下啟動App氛改,我們稱為熱啟動帐萎,相對而言冷啟動就是App被kill掉以后一切從頭開始啟動的過程。我們這里只討論App冷啟動的情況胜卤。
測量Pre-main Time
在Xcode的菜單中選擇Project→Scheme→Edit Scheme...
疆导,然后找到 Run → Environment Variables
配置的 key 為:DYLD_PRINT_STATISTICS
設(shè)置1或者YES都可。
勾選如圖:
運行的時候會進行打痈瘐铩:
what`s the fuck澈段?
main()函數(shù)之前總共使用了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)庫占據(jù)了絕大數(shù),而針對系統(tǒng)級別的動態(tài)庫都是經(jīng)過系統(tǒng)高度優(yōu)化的,不用擔(dān)心時間的花費.開發(fā)者應(yīng)該關(guān)注于自己集成到App的那些動態(tài)庫,這也是最能消耗加載時間的地方.對此Apple建議減少在App里開發(fā)者的動態(tài)庫集成或者有可能地將其多個動態(tài)庫最終集成一個動態(tài)庫后進行導(dǎo)入,盡量保證將App現(xiàn)有的非系統(tǒng)級的動態(tài)庫個數(shù)保證在6個以內(nèi)
.減少Appp的
Objective-C
類,分類
和的唯一Selector
的個數(shù)
這樣做主要是為了加快程序的整個動態(tài)鏈接, 在進行動態(tài)庫的重定位和綁定(Rebase/binding)過程中減少指針修正的使用,加快程序機器碼的生成.減少Objc運行初始化的時間花費.
主要是類的注冊,分類的注冊,唯一選擇器的存在,以及涉及子父類內(nèi)存布局的Non Fragile ivars偏移的更新,都會影響Objective-C運行時初始化的時間消耗.使用
initialize
方法進行必要的初始化工作.用+initialize方法替換調(diào)用原先在OC的+load
方法中執(zhí)行初始代碼工作,從而加快所有類文件的加載速度.
使用load
不要在這里面執(zhí)行耗時的操作。其他的不是短時間能改變的。
測量main Time
一般項目的組織架構(gòu):
didFinishLaunchingWithOptions
在didFinishLaunchingWithOptions
這里面有的是必須執(zhí)行的鹦聪,但是我們可以適當?shù)母鶕?jù)功能的不同對應(yīng)的適當延遲啟動的時機账阻。對于我們項目,我將初始化分為三個類型:
- 日志泽本、統(tǒng)計等必須在 APP 一起動就最先配置的事件
- 項目配置宰僧、環(huán)境配置、用戶信息的初始化 观挎、推送琴儿、IM等事件
- 其他 SDK 和配置事件
第一類,由于這類事件的特殊性嘁捷,所以必須第一時間啟動造成,仍然把它留在 didFinishLaunchingWithOptions 里啟動。
第二類事件雄嚣,這些功能在用戶進入 APP 主體的之前是必須要加載完的晒屎,所以我們可以把它放在第二批,也就是用戶已經(jīng)看到廣告頁面缓升,再進行廣告倒計時的時候再啟動鼓鲁。
第三類事件,由于不是必須的港谊,所以我們可以放在第一個界面渲染完成以后的 viewDidAppear 方法里骇吭,這里完全不會影響到啟動時間。
參考文章歧寺,我們不妨提取出一個工具類燥狰,為了以后防止以后繼續(xù)優(yōu)化
下面是這個類的頭文件:
/**
* 注意: 這個類負責(zé)所有的 didFinishLaunchingWithOptions 延遲事件的加載.
* 以后引入第三方需要在 didFinishLaunchingWithOptions 里初始化或者我們自己的類需要在 didFinishLaunchingWithOptions 初始化的時候,
* 要考慮盡量少的啟動時間帶來好的用戶體驗, 所以應(yīng)該根據(jù)需要減少 didFinishLaunchingWithOptions 里耗時的操作.
* 第一類: 比如日志 / 統(tǒng)計等需要第一時間啟動的, 仍然放在 didFinishLaunchingWithOptions 中.
* 第二類: 比如用戶數(shù)據(jù)需要在廣告顯示完成以后使用, 所以需要伴隨廣告頁啟動, 只需要將啟動代碼放到 startupEventsOnADTimeWithAppDelegate 方法里.
* 第三類: 比如直播和分享等業(yè)務(wù), 肯定是用戶能看到真正的主界面以后才需要啟動, 所以推遲到主界面加載完成以后啟動, 只需要將代碼放到 startupEventsOnDidAppearAppContent 方法里.
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface BLDelayStartupTool : NSObject
/**
* 啟動伴隨 didFinishLaunchingWithOptions 啟動的事件.
* 啟動類型為:日志 / 統(tǒng)計等需要第一時間啟動的.
*/
+ (void)startupEventsOnAppDidFinishLaunchingWithOptions;
/**
* 啟動可以在展示廣告的時候初始化的事件.
* 啟動類型為: 用戶數(shù)據(jù)需要在廣告顯示完成以后使用, 所以需要伴隨廣告頁啟動.
*/
+ (void)startupEventsOnADTime;
/**
* 啟動在第一個界面顯示完(用戶已經(jīng)進入主界面)以后可以加載的事件.
* 啟動類型為: 比如直播和分享等業(yè)務(wù), 肯定是用戶能看到真正的主界面以后才需要啟動, 所以推遲到主界面加載完成以后啟動.
*/
+ (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:@" 等延遲啟動項沒有啟動, 這會造成應(yīng)用奔潰"];
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)控實踐