一、移動應(yīng)用的性能
性能指標(biāo)
- 內(nèi)存
運(yùn)行所需最小 RAM,內(nèi)存平均值和峰值,內(nèi)存泄漏
電量消耗
初始化時(shí)間
惰性初始化所需的對象
- 執(zhí)行速度
并行處理技術(shù)或?qū)?fù)雜任務(wù)分發(fā)到服務(wù)器
響應(yīng)速度
本地存儲
互操作性
-
網(wǎng)絡(luò)環(huán)境
- 高帶寬穩(wěn)定環(huán)境
- 低帶寬穩(wěn)定環(huán)境
- 高帶寬不穩(wěn)定環(huán)境
- 低帶寬不穩(wěn)定環(huán)境
- 無網(wǎng)絡(luò)
帶寬
數(shù)據(jù)刷新
多用戶支持
單點(diǎn)登錄
如果用戶登錄了一個應(yīng)用,則只需要點(diǎn)擊一次,就可以登錄到其他應(yīng)用
安全
崩潰
應(yīng)用性能分析
- 采樣
- 埋點(diǎn)
- 日志(debug、verbose、info、warning、rrror)
二、內(nèi)存管理
iOS 的虛擬內(nèi)存模型不包含交換內(nèi)存,意味著磁盤不能被用來分頁內(nèi)存。iOS 應(yīng)用的內(nèi)存消耗分為兩部分:棧大小和堆大小。
每個進(jìn)程的所有線程共享同一個堆,只有操作系統(tǒng)能管理堆。
循環(huán)引用常見場景
- 委托
在委托中建立對操作的強(qiáng)引用,在操作中建立對委托的弱引用。
塊
線程與計(jì)時(shí)器
NSTimer 創(chuàng)建后會持有創(chuàng)建者,同時(shí)被 runtime 持有,可以調(diào)用 NSTimer 的 invalidate 方法解除資源和持有。
推薦方案:將持有關(guān)系分散到多個類中,任務(wù)類執(zhí)行具體動作,所有者類調(diào)用任務(wù)。從而通過間接層實(shí)現(xiàn)明確的銷毀過程。間接層使用弱引用持有所有者類,以保證所擁有的對象能夠在停止使用后執(zhí)行銷毀動作。
- 觀察者
鍵值觀察與通知中心均不會維持觀察對象、被觀察對象以及上下文的強(qiáng)引用,如有必要,需要自行維護(hù)。
返回錯誤
對象指針本身不會對對象的引用計(jì)數(shù)造成影響,對象指針與對象本身的內(nèi)存管理修飾符應(yīng)當(dāng)一樣。
{
NSError *error = nil;
@autoreleasepool {
NSError * __weak errorWeak = error;
[self performOperationWithError:&errorWeak];
}
NSLog(@"%@", error);
}
- (BOOL)performOperationWithError:(NSError *__weak*)error
{
// 方法執(zhí)行發(fā)生錯誤
*error = [NSError errorWithDomain:@"MyAppDomain" code:123 userInfo:nil];
return NO;
}
NSError** 作為參數(shù)時(shí)應(yīng)該使用 __autoreleasing 修飾符。雖然賦值對象指針時(shí),所有權(quán)修飾符必須一致,但是這里傳參時(shí)編譯器會進(jìn)行優(yōu)化
NSError *error = nil;
NSError __autoreleasing *tmp = error;
BOOL result = [obj performOperationWithError:&tmp];
error = tmp;
單例
單例使用場景
- 隊(duì)列操作(如日志或埋點(diǎn))
- 訪問共享資源(如緩存)
- 資源池(如線程池或連接池)
三、能耗
計(jì)算量的能耗取決于不同因素
- 對數(shù)據(jù)的處理
- 待處理的數(shù)據(jù)大小 —— 更大的顯示器意味著更多的信息和數(shù)據(jù)需要處理
- 處理數(shù)據(jù)的算法和數(shù)據(jù)結(jié)構(gòu)
- 執(zhí)行更新的次數(shù)
最佳實(shí)踐
- 針對不同情況選擇優(yōu)化算法
小于 43 個實(shí)例則插入排序優(yōu)于歸并排序,多于 286 個則快速排序最優(yōu),優(yōu)先使用雙樞軸快排。
- 盡量將數(shù)據(jù)處理放在服務(wù)器端
- 優(yōu)化靜態(tài)編譯處理(AOT)
- 分析電量消耗
網(wǎng)絡(luò)
- 在進(jìn)行網(wǎng)絡(luò)操作前檢查合適的網(wǎng)絡(luò)連接是否可用。
- 持續(xù)監(jiān)視網(wǎng)絡(luò)可用性,并在連接狀態(tài)發(fā)生變化時(shí)給予適當(dāng)?shù)姆答仭?/li>
推薦使用 Reachability Pod 檢測網(wǎng)絡(luò)狀態(tài)。
使用基于隊(duì)列的網(wǎng)絡(luò)請求可以避免服務(wù)器遭到多個同時(shí)發(fā)起的請求轟炸。至少使用兩個隊(duì)列:一個用于通常不關(guān)鍵的大量圖片下載,一個用于關(guān)鍵數(shù)據(jù)請求。
定位管理器
使用 GPS 坐標(biāo)需要兩點(diǎn)信息
- 時(shí)間鎖——每個 GPS 衛(wèi)星每毫秒廣播唯一一個 1023 位隨機(jī)數(shù),GPS 接收芯片必須正確與時(shí)間鎖對齊
- 頻率鎖——GPS 接收器計(jì)算由接收器與衛(wèi)星的相對運(yùn)動導(dǎo)致的多普勒偏移帶來的信號誤差
通常情況下鎖定一顆衛(wèi)星需要至少 30 秒,確定衛(wèi)星越多,取得的坐標(biāo)越精確。
只在必要時(shí)使用網(wǎng)絡(luò)
每當(dāng)應(yīng)用建立網(wǎng)絡(luò)連接時(shí),硬件都會在連接完成后多維持幾秒的活動時(shí)間,集中的網(wǎng)絡(luò)通信都會消耗大量的電量。所以應(yīng)該定期集中短暫地使用網(wǎng)絡(luò)。
(可是埋點(diǎn)、網(wǎng)絡(luò)監(jiān)測模塊應(yīng)該怎么取舍呢?)
屏幕
- 動畫在應(yīng)用進(jìn)入后臺后應(yīng)該暫停
- 視頻播放時(shí)保持屏幕常亮
最佳實(shí)踐
- 最小化硬件使用,盡可能晚地與硬件打交道,一旦完成任務(wù)立即結(jié)束使用
- 進(jìn)行密集型任務(wù)前檢查電池電量和充電狀態(tài)
- 電量低時(shí)提示用戶是否確定要執(zhí)行任務(wù),并在用戶同意后再執(zhí)行
- 提供設(shè)置選項(xiàng),允許用戶定義電量的閾值,以便在執(zhí)行密集型操作前提示用戶
四、并發(fā)編程
每個線程大約消耗 1KB 的內(nèi)核內(nèi)存空間,這塊內(nèi)存用于存儲與線程相關(guān)的數(shù)據(jù)結(jié)構(gòu)和屬性,屬于聯(lián)動內(nèi)存,無法分頁。
主線程的??臻g大小為 1M,并且無法修改,其他二級線程默認(rèn)分配 512KB 棧空間,但是并不會一次性創(chuàng)建完整大小,而是隨著使用逐漸增長。
線程創(chuàng)建的時(shí)間消耗為 4~5 毫秒,啟動線程的時(shí)間消耗為 29 毫秒,主要是上下文切換帶來的時(shí)間開銷。
GCD最大線程個數(shù)為 64 個。
并行讀取、互斥寫入
- 創(chuàng)建一個并行隊(duì)列
- 對于讀操作使用 dispatch_sync
- 對于寫操作使用 dispatch_barrier_sync
dispatch_barrier_sync 可以在并行隊(duì)列上創(chuàng)建一個同步點(diǎn),dispatch_barrier_sync 中的代碼塊會在之前所有提交代碼塊執(zhí)行結(jié)束后單獨(dú)執(zhí)行。
生成器模式
對于數(shù)據(jù)實(shí)體的初始化,一般有兩種方案:
- 使用自定義初始化器
- 使用生成器模式
自定義初始化器需要眾多的參數(shù),和很長的方法名,并且會帶來向下兼容的問題,加入新的屬性將導(dǎo)致調(diào)用初始化器的代碼不能使用。
因此建議使用生成器模式。
交叉引用
實(shí)體之間的交叉引用,尤其是不可變實(shí)體的交叉引用,會在初始化時(shí)陷入循環(huán)初始化陷阱,A 需要 B 來初始化,B 又需要 A 來初始化。此時(shí)可以利用冰棒不變性,用一個標(biāo)志位和一個設(shè)置標(biāo)志位的方法來進(jìn)行一次性初始化操作,之后再對屬性賦值時(shí)需要檢測此屬性來決定是否可以修改。
- (void)freeze
{
self.frozen = YES;
}
- (void)setUserId:(NSString *)userId
{
if (!self.frozen) {
_userId = userId;
}
}