iOS 內存管理:內存優化
所謂的內存優化,在設計程序的過程中,我們要在保證程序運行效率的前提下,盡量壓縮程序運行時所占用的內存。無論硬件設備的內存有多大,程序運行時占用內存越少越好。下面我將介紹在開發項目過程中,一些優化內存的方法。
1.關于UITableView
在項目開發中,UITableView 是用的比較多的一個視圖控件。如果能夠對 UITableView 的使用做好優化,程序的性能將提高很多。
(1)善于使用UITableViewCell的重用機制
重用機制:這種機制下系統默認有一個可變數組 NSMutableArray* visiableCells,用來保存當前顯示的cell。一個可變字典 NSMutableDictnery* reusableTableCells ,用來保存可重復利用的cell。UITableView 只會創建一屏幕的cell,放在 visiableCells中。每當cell滑出屏幕,就會放到 reusableTableCells 中,當要顯示某一個位置的cell時,先去 reusableTableCells 中取,如果有,直接取來用;如果沒有,就會創建。這樣極大減少了內存的開銷。
在iOS 6之后,在UITableView和UICollectionView中除了可以復用cell,還可以復用各個Section的Header和Footer??梢夾pple一直在不斷優化。在項目開發中,我們需要給 UITableViewCells、 UICollectionViewCells、UITableViewHeaderFooterViews設置正確的 reuseIdentifier。當有多類cell需要復用是,我們可以根據 reuseIdentifier 區分。我們可以在Xcode中設置,如下圖:
下面是一個簡單的cell復用的示例:
- - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
- static NSString *cellIdentifier = nil;
- UITableViewCell *cell = nil;
- cellIdentifier = @"你的xib文件視圖中標注的reuseIdentifier";
- cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; //根據identifier復用cell
- //如果沒有對應的cell,創建cell
- if(!cell){
- cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
- }
- return cell;
- }
復用cell是一個很好的機制,但是使用不當也會出現問題,也就是所謂的復用重疊問題??聪旅娲a:
我本打算將偶數行的設置為藍色,基數行為默認顏色,并將cell的內容設置為行數,加以區分。結果如圖:
從上圖可以看出,開始初始化的13~14個cell正常,但是當滑動tableview時,就出現了問題,有的基數行cell也變為了藍色。這是因為,下面的cell基本都是復用的,當沒有顯示指定cell的屬性時,它就會使用已經創建過的cell的屬性,導致有的藍色有的白色。解決辦法就是像下面這樣寫:
切記:當對多種cell賦予屬性時,一定不能寫在 if (!cell){} 里面,避免復用出現問題。
(2)優化UITableViewCell高度計算
UITableView有兩個很重要的回調方法:tableView:cellForRowAtIndexPath:和tableView:heightForRowAtIndexPath:。很多人認為,在初始化tableview時,會先調用前者進行創建,然后再調用后者進行布局和屬性設置。然而并非如此。真實的情況是這樣的:UITableView是繼承自UIScrollView的,需要先確定它的contentSize及每個Cell的位置,然后才會把重用的Cell放置到對應的位置。所以事實上,UITableView的回調順序是先多次調用 tableView:heightForRowAtIndexPath: 以確定contentSize及Cell的位置,然后才會調用 tableView:cellForRowAtIndexPath:,從而來顯示在當前屏幕的Cell。
舉個例子:如果現在要顯示20個Cell,當前屏幕顯示5個。那么刷新(reload)UITableView時,UITableView會先調用20次 tableView:heightForRowAtIndexPath: 方法,然后調用5次tableView:cellForRowAtIndexPath:方法;滾動屏幕時,每當Cell滾入屏幕,都會調用一次tableView:heightForRowAtIndexPath:、tableView:cellForRowAtIndexPath:方法。
所以,對于UITableViewCell的高度計算的優化,就是對這兩個函數的處理。至于如何優化@我就叫Sunny怎么了寫了一篇很好的文章去介紹。我就不多說了。
(3) 懶加載(延遲加載)
懶加載并不是減少了程序內存消耗,而是將加載對象的時間推遲,在使用到對象的時候在對其進行初始化。例如一個UITableView一共有20行,但是屏幕只顯示5行數組。那么在初始化tableview的時候,可以只先加載5行數據,另外15行等到顯示的時候再去加載。這樣可以減少初始化tableview時所需要的內存。(這樣說有點牽強,因為實時加載會影響tableview的流暢度,但是大體就是這個意思 ><)
2.關于圖片的處理
圖片在內存中會占很大開銷,如果適當的處理圖片,會減少很多內存的消耗。
(1)緩存圖片
常見的從bundle中加載圖片的方式有兩種,一個是用imageNamed,二是用imageWithContentsOfFile,***種比較常見一點。
imageNamed的優點是當加載時會緩存圖片。imageNamed的文檔中這么說:
這個方法用一個指定的名字在系統緩存中查找并返回一個圖片對象如果它存在的話。如果緩存中沒有找到相應的圖片,這個方法從指定的文檔中加載然后緩存并返回這個對象。
也就是說,imageNamed方法加載的圖片,會對圖片進行緩存。而 imageWithContentsOfFile 方法不會。
所以,如果要加載的圖片比較小,而且會反復使用,這種情況選擇用 imageNamed;如果要加載一個大圖片,而且是一次性使用,那就使用 imageWithContentsOfFile,沒必要浪費內存去緩存它。
代碼示例:
(2)調整圖片大小
我們經常從網絡獲取圖片或者從本地bundle獲取圖片,然后加載到 UIImageView 中。在加載圖片時,應盡量保證圖片大小和 UIImageView 大小相同。因為在運行中縮放圖片很耗費資源,如果 UIImageView 嵌套在 UIScrollView 或者 UITableView中,會更耗費資源。
對于從本地bundle中加載的圖片,我們可以事先件圖片處理好。對于從網絡下載的圖片,在下載完成后,我們需要對圖片進行縮放,然后再加載。
(3)代碼渲染 or 直接獲取
前面已經說過,用代碼去渲染一張圖片會使圖片占用內存翻倍。但是用代碼去繪制圖片,能夠很好的去控制圖片,并且能夠做出很多漂亮的效果,前提是犧牲一部分內存;那如果所有圖片都從bundle中加載呢?那會使bundle的體積增大,同時不能夠用代碼去靈活處理圖片的效果。
所以,在開發過程中,是代碼渲染圖片,還是從bundle獲取圖片,需要做一個權衡。
3.數據處理
在項目開發中,我們會使用到各種格式的數據,例如 JSON、XML 等。還有各種各樣的數據結構,例如數組、鏈表、字典、集合等。使用正確的數據格式和使用正確的數據結構,會減少我們的資源消耗。
(1)選擇正確的數據格式
App與網絡進行交互時,常常采用 JSON 或者 XML 類型的數據格式。
JSON 是一種輕量級的數據交換格式,具有良好的可讀和便于快速編寫的特性。解析 JSON 會比 XML更快,但是 JSON 傳輸的數據比較小。
XML 是一種重量級的數據交換格式,適用于很大的數據傳輸。當數據量較大時,使用 XML 數據格式,會極大減少內存消耗,增加性能。
另外,盡量避免數據多次轉化。例如tableview中需要以數組的形勢去賦值。那么服務器盡量返回數組類型。如果返回 JSON 類型,在去轉換為 NSArray 類型,也會增加開銷。
(2)選擇正確的數據結構
不同的數據結構,處理數據的速度是不同的。
數組 NSArray NSMutableArray:有序的一組值。使用索引來查詢很快,使用值查找很慢, 插入/刪除很慢。
字典 NSDictionary NSMutableDictionary:存儲鍵值對。用鍵來查找比較快。
集合 NSSet NSMutableSet:無序的一組值。用值來查找很快,插入/刪除很快。
4.View的處理
(1)避免使用過于復雜的xib
在目前很多項目開發中,還經常用到 xib。當加載一個 xib 時,所有的內容都會放到內存里,包括任何圖片。如果 xib 文件過于龐大,會占用很多內存。xib 與 storyboard 不同,xib即使暫時用不到,view也會存在于內存里;storyboard 僅在需要時實例化一個view controller。
而且設置view屬性時,盡可能的把 opaque 屬性設置為YES(不透明)。這樣會提高渲染系統優化一些渲染過程和提高性能。
(2)正確設置View的背景
設置UIView的背景圖片主要有兩種方式:
使用 UIColor的 colorWithPatternImage 來設置背景色;
給 UIView 添加 UIImageView 子視圖。
***種方式,適合使用小圖平鋪創建背景,能更快渲染也不會會費很多內存。例如使用一個10x10的像素大小重復背景。
第二種方式,適合于使用大圖,即整張圖片來設置背景。如果使用 colorWithPatternImage 會消耗太多內存從而收到內存警告導致應用程序突然崩潰。而使用 UIImageView 會節約不少內存。
(3)設定Shadow Path
如果用下面代碼給 view.layer 添加一個shadow:
這會使Core Animation 不得不在后臺得出圖形并加好陰影之后再去渲染,這會開銷很大。
如果使用shadowPath則會避免這種問題:
5.合理使用Autorelease Pool
NSAutoreleasePool負責釋放block中的autoreleased objects。一般情況下它會自動被UIKit調用。但是有些狀況下也需要手動去創建它。
假如創建很多臨時對象,你會發現內存一直在減少直到這些對象被release的時候。這是因為只有當UIKit用光了autorelease pool的時候memory才會被釋放。
但是如果自己定義 @autoreleasepool ,在里面創建臨時對象,可以避免這個問題:
6.正確處理緩存
緩存可以分為內存緩存和磁盤緩存。在項目開發過程中,我們經常會對一些圖片、聲音、數據進行緩存。合理利用緩存機制,會大大提高程序的性能,提高APP的流暢性。例如被廣為使用的 SDWebImage,它使用的緩存機制是這樣的:
(1)先根據查看內存緩存,如果有直接獲取。
(2)如果內存沒有,從磁盤緩存獲取。
(3)如果磁盤緩存也沒有,直接通過URL從網絡下載。
當然這只是一個簡單的描述,更加詳細請看@南峰子_老驢的一篇SDWebImage實現分析。
合理處理緩存,能夠提高程序的性能,不用每次都從網絡獲取數據。但是也不能什么都存入緩存,這會消耗很多內存和磁盤空間。所以應合理使用緩存機制。
小結
以上,是我對于內存優化的一些理解。在寫這篇文章過程中,參考了很多大牛的文章。對于一名在校應屆本科生來說,我對于oc的理解還很淺薄,如果有錯誤或者有需要添加的地方,希望大家能夠指出。我會加以改正并學習。