作者:Thomas Hanning,原文鏈接,原文日期:2015/08/06
譯者:BridgeQ;校對:lfb-CD;定稿:shanks
近年來,移動設備的性能越來越強大。然而,同桌面電腦相比,性能上總還是有一段不小的差距。同時,用戶界面和交互設計的要求也越來越高。所以,為移動設備編寫內存高效的應用仍然很有必要。
什么是內存高效的應用
通俗點講,內存高效的應用是指:僅使用必要的內存消耗并盡量減少內存消耗;用戶界面設計使用低內存消耗的框架。當然,一個復雜度更高的應用也肯定需要更高的內存消耗。
接下來我們首先來回顧一小段歷史:
自動引用計數(ARC:Automatic Reference Counting)
在早期的 iOS 開發中,內存管理扮演著非常重要的角色。因為傳統的垃圾回收機制對于移動平臺來說是非常低效的,所以蘋果把內存管理的責任交給了開發者,你需要通過手動的方式來增加或減少一個對象的引用計數。
通過這種方式,你可以寫出內存管理非常高效的應用,因為對象不再使用時就立刻被銷毀了。但另一方面,很多時候手動管理內存并不容易,也經常產生一些不易被發現的bug。所以,這并不是解決內存管理問題的最好辦法。
新的解決方案是在 iOS 5 中被提出的:自動引用計數(ARC)。從此之后,控制引用計數的命令會在編譯期間被自動加入而無需手動編寫。這樣帶來的好處是:一方面能編寫出內存高效的代碼,另一方面讓開發者不用再關心內存管理問題。這個解決方案非常棒,以至于 Mac OS X 的應用程序也開始使用 ARC 來管理內存。
不過,盡管你不需要再關心引用計數了,但還是需要你去關心一些其他內存管理相關的知識點。
選擇合適的部署版本
正如前文所說,不同的需求意味著應用的內存消耗也不盡相同,除此之外,不同的需求還意味著應用適應于不同的部署版本(應用運行所支持的最低系統版本)。舉個栗子,如果你的部署版本是 iOS 5,那么你就不能忘了要支持第一代 iPad 產品,它的內存大小僅僅是 256 MB。雖然,支持盡可能多的版本是一種不錯的選擇,但是,在你所支持的版本上為用戶帶來良好的用戶體驗才是更重要的。所以,當你想支持一些舊設備的時候,在設計階段就要仔細考慮內存消耗問題。
下面列舉了不同系統版本所支持的一些舊設備:
- iOS 9: iPhone 4S / iPad 2 / iPad Mini 1
- iOS 8: iPhone 4S / iPad 2 / iPad Mini 1
- iOS 7: iPhone 4 / iPad 2 / iPad Mini 1
- iOS 6: iPhone 3GS / iPad 2/ iPad Mini 1
- iOS 5: iPhone 3GS / iPad 1 / –
由于蘋果的生態系統更新速度比較快,所以支持最新的兩代操作系統版本是一個很好的選擇。除了內存方面的問題,支持過多的系統版本還會帶來開發和測試等諸多方面的問題。
圖片資源
在移動應用中,圖片是非常重要的資源。然而,圖片也通常代表著高內存消耗。在處理圖片資源的時候,有以下兩點需要注意:
- 首先,圖片應該有合適的尺寸。如果你有一個表格視圖,上面需要 100 × 100 像素的圖片,而你卻使用 1000 × 1000 像素的圖片,這一定是一個非常糟糕的作法,性能會受到非常大的影響。如果你是通過請求服務器獲取的圖片,那么服務器也應該進行處理以提供合適尺寸的圖片。
- 其次,一定要保證圖片只是在需要使用的時候才被加載。舉個栗子,表格視圖里的圖片只有當單元格顯示出來的時候才需要被加載,也就是說單元格是可以循環利用的。想象一下,如果你的表格視圖有 5000 個單元格并在進入屏幕的時候全部被加載,這樣的話,即使你的應用沒有因為內存壓力而崩潰,用戶體驗也一定會非常的糟糕。你自定義的視圖也同樣應該遵守這一原則。再舉個栗子,如果你在開發一個相冊類應用,不要一口氣把所有圖片都加載完,你只需加載顯示在屏幕上的那些圖片就夠了。這種技術也通常被稱作延遲加載(lazy loading)。
延遲加載(lazy loading)
延遲加載技術的主旨就是盡可能晚地去加載資源,這樣會帶來以下兩點優勢:
- 可以更好地來分配不同的加載時間
- 可以避免加載那些可能不需要的資源
那么在 iOS 開發中如何使用延遲加載技術呢?正如前文提到的,表格視圖就是一個很好的使用延遲加載技術的栗子。另一種使用延遲加載的方法是使用lazy
關鍵字來修飾屬性。想象一下你需要一個包含所有產品的數組,當用戶進行一定交互時需要使用到它們。
var products: [Products] = modelClass.loadProducts()
如上代碼,這個數組即使在用戶沒有進行任何交互的情況下仍然會被加載,這是一種內存浪費。如果加上lazy
關鍵字進行修飾,那么只有在用戶第一次訪問數組的時候它才會初始化。
lazy var products: [Products] = modelClass.loadProducts()
即使只是一些小的數組和變量,合理地使用延遲加載技術也能節省很大一部分內存。
視圖控制器和循環引用
在所有內存問題中最壞的一種情況就是視圖控制器不再需要的時候卻沒有被釋放,出現這種情況最通常的原因是循環引用。試想一下,現在有一個控制器 A,它擁有一個控制器 B 作為它的子控制器,而且,控制器 B 還有一個指向控制器 A 的引用,這樣它們都互相強引用著對方。
現在,即使控制器 A 從屏幕中離開,兩個控制器也不會被釋放,因為它們還都強引用著對方。要避免這種情況你可以使用weak
關鍵字。舉個栗子,想要將控制器 A 設置為控制器 B 的代理,正確的屬性聲明應該如下所示:
weak var delegate: DelegateType?
如果想檢查控制器是否被正確釋放,可以在控制器的deinit
方法中打印消息來查看,代碼如下:
deinit {
println("deinit")
}
接下來你就可以通過在控制臺中觀察,是否有輸出來檢查控制器對象是否被正確釋放。比如說,當你的控制器是被導航控制器通過push
方法展現出來的時候,如果你點擊了導航條上的返回按鈕,控制器應該被釋放并且在控制臺中輸出信息。
監測你的內存使用量
我們通常是在項目開發的最后階段才發現內存管理的很糟糕,不幸的是,這樣已經太晚了。所以在項目開發過程中,經常對內存使用量進行監測是非常重要的。你只需在一臺真機上運行你的應用,然后點擊Xcode中調試選項卡下的Memory
。

總結
內存管理在移動開發領域是一個非常重要的話題。如果你使用了過多的內存消耗,應用就會變慢甚至可能崩潰。相反,如果你認真對待內存管理問題,你就會構建出內存高效的應用。