一、app啟動原理
1.app啟動分為冷啟動和熱啟動。
App 的啟動主要包括三個階段:main() 函數(shù)執(zhí)行前;main() 函數(shù)執(zhí)行后;首屏渲染完成后。
1. main() 函數(shù)執(zhí)行前;
加載Mach-o文件;加載動態(tài)庫;Objc類、分類、方法初始化;+load()方法初始化。
可以優(yōu)化的點:減少動態(tài)庫加載,即合并動態(tài)庫;采用懶加載的方式調(diào)用類或方法;+load()使用+initialize()方法替換;控制C++全局變量數(shù)量。
2. main() 函數(shù)執(zhí)行后
從main()函數(shù)到didFinishLaunchingWithOptions方法里首屏渲染完成。
一般app的配置信息,初始化,信息上報等都在這里。放在了首屏渲染之前。
可以優(yōu)化的點:按功能梳理出首屏必要的初始化功能。
3. 首屏渲染完成后
主要完成的是,非首屏其他業(yè)務(wù)服務(wù)模塊的初始化、監(jiān)聽的注冊、配置文件的讀取等
問題:什么樣的功能適合放在首屏渲染后呢?應(yīng)該先檢測方法耗時,按耗時嚴重的去重點關(guān)注。
app啟動速度的監(jiān)控
第一種:定時抓取主線程的方法調(diào)用棧,計算一段時間里的各個方法耗時。
第二種:對objc_msgSend方法進行hook來掌握所有的方法執(zhí)行耗時。
自定義方法耗時工具
fishhook:在 iOS 上運行的 Mach-O 二進制文件中動態(tài)地重新綁定符號
二、啟動時加載邏輯梳理
1. 通訊之前有哪些邏輯?
- 請求LBS接口(獲取socket連接地址)
- 拿到IP、端口后socket連接
2. 一款I(lǐng)M軟件,啟動時需要哪些邏輯。
- 獲取服務(wù)端時間
- 同步拉取個人信息、名片信息、同步個人設(shè)置(3個接口)
- 同步好友信息、同步最近聯(lián)系人(2個接口)
- 同步組織信息
- 同步群組資料信息
- 同步群成員列表及禁言信息(2個接口)
思考及優(yōu)化
LBS接口的作用
LBS僅返回一個地址,首先必須返回是IP地址,避免DNS解析耗時。
LBS接口還有一個作用,就是負載均衡,服務(wù)端有多臺服務(wù)器,但那臺服務(wù)器處于空閑,由服務(wù)端返回最優(yōu)的IP.LBS的接口是HTTP的,在網(wǎng)絡(luò)不穩(wěn)定時,也是非常耗時的
對LBS返回的ip做緩存,當ip連接失敗時,再請求LBS接口第二步的邏輯有些多
接口能否合并,如個人信息與個人設(shè)置:對于功能不同的接口更愿意遵循單一職責(zé)原則,合并到一起反而顯得臃腫。可以采用第4步的方式來解決這個問題。
是否所有消息都是必須拉取,如群成員列表是否進入會話才關(guān)注。
已經(jīng)加載過的,考慮增量拉取。不要把所有接口都串行,屢清幾條線并發(fā)。之前的邏輯:
dispatch_group_async(syncGroup, syncQueue, ^{
BLLogDebug(@"sync");
[self getNetworkTimestamp:^{
// BLLogDebug(@"【INFO】1、獲取服務(wù)器時間成功");
dispatch_semaphore_signal(syncSemaphore);
}];
dispatch_semaphore_wait(syncSemaphore, DISPATCH_TIME_FOREVER);
[self syncLoginUserProfileWithCallback:^{
// BLLogDebug(@"【INFO】2、同步賬戶信息成功");
dispatch_semaphore_signal(syncSemaphore);
}];
dispatch_semaphore_wait(syncSemaphore, DISPATCH_TIME_FOREVER);
[self syncFriendsWithCallback:^{
// BLLogDebug(@"【INFO】3、同步好友信息成功");
dispatch_semaphore_signal(syncSemaphore);
}];
dispatch_semaphore_wait(syncSemaphore, DISPATCH_TIME_FOREVER);
[self syncEnterprisesWithCallback:^{
// BLLogDebug(@"【INFO】4、同步組織信息成功");
dispatch_semaphore_signal(syncSemaphore);
}];
dispatch_semaphore_wait(syncSemaphore, DISPATCH_TIME_FOREVER);
[self syncImGroupWithCallback:^{
BLLogInfo(@"【INFO】5、同步群組信息成功");
dispatch_semaphore_signal(syncSemaphore);
}];
dispatch_semaphore_wait(syncSemaphore, DISPATCH_TIME_FOREVER);
[self syncImgroupMemberWithCallback:^{
BLLogInfo(@"【INFO】6、同步群組成員信息成功");
dispatch_semaphore_signal(syncSemaphore);
}];
dispatch_semaphore_wait(syncSemaphore, DISPATCH_TIME_FOREVER);
});
dispatch_group_notify(syncGroup, syncQueue, ^{
// BLLogDebug(@"【INFO】7、同步完畢");
[self syncComplete];
});
修改完后:
- 從所有串行到并發(fā)三條線:同步賬戶信息;同步好友信息;群組及群成員(必須串行)
- 同步賬戶信息:同步拉取個人信息、名片信息、同步個人設(shè)置,在個人信息返回后就算完成,名片信息和同步個人設(shè)置慢慢拉取。
- 獲取服務(wù)器時間,放在最后。前面的接口都有返回時間。
dispatch_queue_t syncConcurrentQueue = dispatch_queue_create("sync", DISPATCH_QUEUE_CONCURRENT);
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
BLGCDGroupManager * gcdGroup = [[BLGCDGroupManager alloc] initWithGroup:syncGroup queue:syncConcurrentQueue];
[gcdGroup enter];
dispatch_group_async(syncGroup, syncConcurrentQueue, ^{
[self syncLoginUserProfileWithCallback:^{
BLLogWarn(@"<<< t3_1_0 同步賬戶信息 %f",CFAbsoluteTimeGetCurrent()-startTime);
[gcdGroup leave];
}];
});
[gcdGroup enter];
dispatch_group_async(syncGroup, syncConcurrentQueue, ^{
[self syncFriendsWithCallback:^{
BLLogWarn(@"<<< t3_1_1 同步好友信息 %f",CFAbsoluteTimeGetCurrent()-startTime);
[gcdGroup leave];
}];
});
[gcdGroup enter];
dispatch_group_async(syncGroup, syncConcurrentQueue, ^{
[self syncImGroupWithCallback:^{
[self syncImgroupMemberWithCallback:^{
BLLogWarn(@"<<< t3_1_2 同步群組、群組成員信息 %f",CFAbsoluteTimeGetCurrent()-startTime);
[gcdGroup leave];
}];
}];
});
[gcdGroup enter];
dispatch_group_async(syncGroup, syncConcurrentQueue, ^{
[self syncEnterprisesWithCallback:^{
BLLogWarn(@"<<< t3_1_3 同步組織信息 %f",CFAbsoluteTimeGetCurrent()-startTime);
[gcdGroup leave];
}];
});
dispatch_group_notify(syncGroup, syncConcurrentQueue, ^{//7、同步完畢
BLLogWarn(@"<<< t3_1 同步登錄信息、好友、群組、群成員耗時: %f",CFAbsoluteTimeGetCurrent()-startTime);
[self syncComplete];
[self getNetworkTimestamp:nil];
});
三、啟動時消息加載慢問題排查
收到反饋:一條信息從啟動或退到后臺后再進入app,新消息展示大概6、7s。
一個IM軟件,這肯定是不能容忍的。
新消息展示流程
啟動幾個時間段:如從didFinishLaunchingWithOptions->首頁消息展示經(jīng)過了哪些過程。
- 請求lbs
- 鏈接socket
- socket鏈接后接收服務(wù)端推送的消息
- 收到消息后進行首屏?xí)捪秩九c紅點展示
思考
偶現(xiàn)的問題。聽到反饋,先確定原因。
排查原因:首先希望測試能復(fù)現(xiàn);其次從代碼角度排查。
- 打印出各時間段耗時與總耗時,總時間超過5s進行上報。
- 服務(wù)端從socket連接上、到消息推送耗時加日志。
解決
- 發(fā)現(xiàn)最耗時的時間段:在socket連接成功后到消息推到客戶端占了5s多。
- 服務(wù)端邏輯問題:先查詢所有會話,再去過濾未讀的會話推送給客戶端。當會話量大時,耗時嚴重。
- 服務(wù)端直接查詢未讀的會話,再做其他處理。限制在400ms。
四、啟動拉取個人信息偶現(xiàn)閃退
1.dispatch_group_t
場景:一般在并發(fā)多個網(wǎng)絡(luò)請求都返回時,處理邏輯會用到。
dispatch_group_enter與dispatch_group_leave需要成對出現(xiàn)。但dispatch_group_leave出現(xiàn)比dispatch_group_enter多時會崩潰。
例如在網(wǎng)絡(luò)異常時,出現(xiàn)超時重試多次回調(diào)了block。這種場景不是必現(xiàn),也不好復(fù)現(xiàn),bugly監(jiān)控只能看到在某個函數(shù)崩潰。我認為直接使用系統(tǒng)提供的是不安全的。

我的解決方案:
- 封裝dispatch_group_t使用,當leave出現(xiàn)異常時使用NSAssert處理
@interface BLGCDGroupManager()
{
dispatch_group_t _group;
dispatch_queue_t _queue;
NSInteger _count;
NSLock * _lock;
}
@end
@implementation BLGCDGroupManager
- (instancetype)initWithGroup:(dispatch_group_t)group
queue:(dispatch_queue_t)queue{
self = [super init];
if (self) {
_group = group;
_queue = queue;
_lock = [[NSLock alloc] init];
}
return self;
}
- (void)enter{
if (_group) {
[self add];
dispatch_group_enter(_group);
}
}
- (void)leave{
if (_group && _count > 0) {
[self sub];
dispatch_group_leave(_group);
}else{
NSAssert(@"leave使用出現(xiàn)異常", nil);
}
}
- (void)add{
[_lock lock];
_count ++;
[_lock unlock];
}
- (void)sub{
[_lock lock];
_count --;
[_lock unlock];
}
@end
- 業(yè)務(wù)上的處理
找出dispatch_group_leave為什么會出現(xiàn)兩次。