WWDC筆記-記憶體策略

騎著jm的hi發表於2019-03-01

原文地址

儘管在進入後臺之後,程式的工作受到大幅度的限制,但是我們總是不會希望應用突然被作業系統殺死,中斷了重要的後臺工作。後臺應用被殺死,影響的不止是使用者體驗,比如正在播放的音樂戛然而止,正在導航的語音意外中斷。由於作業系統殺死後臺應用並不是任何一種異常、或者執行錯誤,應用失去響應意外的能力,極有可能破壞了單次執行中積累的重要資料。因此如何減少應用在後臺執行被殺死,是一個值得思考的問題

後臺應用如何被殺

拋開因為應用在後臺停留太久被殺死之外,因為記憶體問題被殺死是最重要的原因。記憶體和其他硬體資源有很大的不同,區別在於:

當資源需求量多於資源所能供應的時,CPU表現為效能下降,而作業系統會為了回收記憶體殺死後臺應用

我們可以把裝置記憶體分成四塊區域,包括systembackgroundforegroundfree四部分。

  • system在裝置開機後是固定佔用的,幾乎不可能被我們的應用訪問
  • background屬於被後臺執行的應用使用,記憶體不足時會被回收
  • foreground屬於當前執行應用使用的記憶體
  • free屬於可分配記憶體,系統會優先分配這部分記憶體給應用使用

假設裝置剛開機,使用者直接開啟你的應用,這時候裝置的記憶體不包括background部分:

WWDC筆記-記憶體策略

應用在這時退出前臺,由於此時還有足夠的free記憶體可分配,系統暫時不會收回記憶體:

WWDC筆記-記憶體策略

然後使用者開啟了一個新的應用,但是新的應用需要大量記憶體,此時超出了裝置的可用記憶體量:

WWDC筆記-記憶體策略

於是系統向你的應用發出memory warning通知,系統回收了暫時不用的記憶體,然後有足夠的記憶體給新應用使用:

WWDC筆記-記憶體策略

又或者根本沒發出warning,你的應用直接被殺死:

WWDC筆記-記憶體策略

記憶體警告

記憶體警告memory warning會在系統察覺到可能沒有更多的free記憶體使用時發出,包括通知和代理兩種方式的調起。通常來說,開發者和系統都會針對warning做應對措施,根據處理方的角色不同,我們可以將其分為主動策略被動策略

記憶體分類

在瞭解兩種應對策略的差異之前,我們需要了解對於每一個執行的應用來說,可被系統自動清除的記憶體分為兩類:(此處記憶體限制為主動分配,可隨時釋放的記憶體)

WWDC筆記-記憶體策略

  • Dirty

    左側正在展示的內容會被我們使用array或者dictionary儲存起來。多數時候,為了更流暢的效果,我們可能會快取頁面外的資料,比如預載入單元格、預計算高度等。這些資料決定了當前應用的狀態、內容等,是活躍資料。這部分記憶體不會被系統回收,會一直儲存直到應用終止

  • Purgeable

    右側儲存了可能使用過的資料,比如使用者進入推薦功能後請求了大量的資料,使用NSCache快取在記憶體中,以便重新進入推薦頁時快速的展示內容,這部分資料是明顯的未使用資料。這部分的記憶體會被系統直接清除後很快的重新分配使用

從記憶體的分類來看,減少Dirty資料的數量,增加Purgeable可以減少記憶體使用的壓力

被動策略

之所以要先說被動策略,是因為作業系統在發出warning之前,已經做了一些工作來嘗試解決問題,只有瞭解這些才能更好的幫助我們解決問題

  • NSPurgeableData

    前面說了被標記為purgeable的記憶體會被系統直接清理資料然後重新分配使用,這個清理的過程發生在發出警告之前。如果在清理purgeable之後就有足夠的記憶體可用了,就不會發出警告。NSPurgeableDataNSData的子類,它提供了類似dispatch_group_t的介面,使用一個整型變數儲存使用狀態,當值為0時,表示資料處於未使用狀態

      NSPurgeableData *purgeableData = [[NSPurgeableData alloc] initWithBytes: fileData.bytes length: fileData.length];
      [purgeableData beginContentAccess];
      /// 值+1,開始使用資料
      
      ......
      
      [purgeableData endContentAccess];
      /// 值-1,結束使用資料
      
      ......
      /// 過了很長時間,如果無法訪問資料,說明被回收,需要重新建立
      if (![purgeableData beginContentAccess]) {
          purgeableData = [[NSPurgeableData alloc] initWithBytes: fileData.bytes length: fileData.length];
      }
      [purgeableData beginContentAccess];
    複製程式碼
  • NSCache

    NSCache提供了一套類似dictionary的簡單介面讓我們存取資料,同時允許我們設定一個快取最大值。NSCache的資料被認為是purgeable型別,這些資料會在快取達到最大值時以LRU演算法淘汰使用最少者,也會被系統在發出警告前回收

  • memory warning

    清理purgeable資料後,如果記憶體還是不夠用,這時候系統就會向所有活躍應用發出記憶體警告,此時進入開發者的主動應對環節

主動策略

從操作上來說,purgeable的資料都需要開發者維護,更像是一種主動性的機制。但這個機制在記憶體不夠用時,會被系統自動收回記憶體,而無需開發者進行更多額外的工作,因此被我歸類到被動策略。主動策略更應該是接收到memory warning後,開發者進行的工作:

- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
    /// release data cannot keep the application running
    [self.database close];
}

- (void)didReceiveMemoryWarningNotification: (NSNotification *)notification {
    /// release data cannot keep the application running
    [self.dataSource removeAllObjects];
    [self.images removeAllObjects];
    [self cleanAllAnimatorViews];
}
複製程式碼

處理warning幾乎是開發者的唯一有效手段,但是這種手段也並不完全可靠,如果依賴於系統發出的記憶體警告,存在幾個風險點:

  • memory warning是針對所有執行應用發出的,這意味著同一時間,CPU可能忙不過來處理你的工作
  • 如果恰好屬於執行應用佇列的後列,在purgeable資料被回收、且其他應用響應完警告後,依舊沒有足夠記憶體,有可能直接被殺死而沒有處理的機會

因此,即便處理警告幾乎是唯一手段,依舊還得思考其他方式。假如我們從將一部分資料對映到磁碟上,應用可以以最小的記憶體佔用執行

檔案對映

檔案對映不是萬能藥,每次修改資料都會同樣更新到磁碟相同位置,即便SSD的存取能力已經很強大了,但依舊是不小的開銷。你的資料應該總是滿足這些要求,才考慮使用檔案對映:

WWDC筆記-記憶體策略

  • 應用執行的必要資料。比如音樂應用的播放音樂資訊、播放器類
  • 構造或者還原的成本很高。如果成本不高,那麼重新構建並沒有什麼損失
  • 資料便於計算。如果資料總是變化的,會對檔案大小的計算造成嚴重干擾
  • 資料幾乎不發生改變。變化越少,儲存價值越高

這些條件決定了檔案對映這種方式並不是適用於所有資料,基本適用於配置檔案、影象資料、文字資料等,而這些資料也往往是佔用記憶體的大頭

WWDC筆記-記憶體策略

另外如果資料量很大時,將資料整合成一個大資料段。同樣資料大小的情況下,系統處理多塊檔案對映的記憶體效率要低的多。另外太多的虛擬記憶體塊,也會導致系統強行殺死應用。NSData允許我們使用options提供檔案對映的方式:

typedef NS_OPTIONS(NSUInteger, NSDataReadingOptions) {
    NSDataReadingMappedIfSafe =   1UL << 0,
    NSDataReadingUncached = 1UL << 1,
    NSDataReadingMappedAlways API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0)) = 1UL << 3,
};
複製程式碼

其他

由於蘋果的沙盒機制,我們過去總是更多的關注於應用內對於記憶體的使用情況,比較少關注應用在後臺執行時可能遇到的記憶體問題,引用WWDC視訊上蘋果工程師的一句話:

你應該總是認為:在你應用之外的其他東西,會對你保持敵意並且有毀滅你的可能

因此,我們要學習如何合理的運用記憶體使用策略,來抵抗應用外的進攻。當然,最重要的是,我們可以理直氣壯的告訴QA:哼,應用的記憶體使用降下來了!

關注我的公眾號獲取更新資訊

相關文章