《高性能iOS應(yīng)用開發(fā)》核心優(yōu)化

一、移動應(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;
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容