iOS開發——內存優化

前言:

當app通過一段兒時間的迭代,每每會出現一些性能問題,這時可以協助開發解決這些性能問題也成爲咱們測試的重要工做。湊巧最近一段時間就一直在協助開發去進行app內存優化。這裏整理了一份關於內存優化的心得分享給你們。javascript

目的:

首先咱們先要明確咱們的目的,在保證程序運行流暢的前提下儘量的優化使用內存。因此千萬不要掉進爲了優化而優化的陷阱。程序要先保證能運行,而後再談良好運轉。因此解決問題比較寬泛,必要的時候在交互或者運起色制上小動刀子來保證既能完成任務,程序又能正常運轉,沒必要去對代碼進行徹底重構。畢竟在優化效率達到上限的時候,只能用時間換空間了。java

1. 用ARC管理內存web

ARC(Automatic ReferenceCounting, 自動引用計數),它避免了最多見的因爲咱們忘記釋放內存所形成的內存泄露。它自動爲你管理retain和release的過程,因此你就沒必要去手動管理了。編寫代碼的時候很容易忘掉結尾的release。而ARC會自動在底層爲你作這些工做。除了幫你避免內存泄露,ARC還能夠幫你提升性能,它能保證釋放掉再也不須要的對象的內存。面試

2. 在正確的地方使用 reuseIdentifier編程

一個開發中常見的錯誤就是沒有給UITableViewCells, UICollectionViewCells設置正確的reuseIdentifier。json

爲了性能最優化,table view用tableView:cellForRowAtIndexPath:爲rows分配cells的時候,它的數據應該重用自UITableViewCell。一個table view維持一個隊列的數據可重用的UITableViewCell對象。緩存

不使用reuseIdentifier的話,每顯示一行table view就不得不設置全新的cell。這對性能的影響但是至關大的。服務器

自iOS6起,除了UICollectionView的cells和補充views,你也應該在header和footer views中使用reuseIdentifiers。網絡

想要使用reuseIdentifiers的話,在一個table view中添加一個新的cell時在data source object中添加這個方法:數據結構

staticNSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

這個方法把那些已經存在的cell從隊列中排除,或者在必要時使用先前註冊的nib或者class創造新的cell。若是沒有可重用的cell,你也沒有註冊一個class或者nib的話,這個方法返回nil。

3.儘可能把views設置爲透明

若是你有透明的Views你應該設置它們的opaque屬性爲YES。緣由是這會使系統用一個最優的方式渲染這些views。這個簡單的屬性在IB或者代碼裏均可以設定。

Apple的文檔對於爲圖片設置透明屬性的描述是:

(opaque)這個屬性給渲染系統提供了一個如何處理這個view的提示。若是設爲YES,渲染系統就認爲這個view是徹底不透明的,這使得渲染系統優化一些渲染過程和提升性能。若是設置爲NO,渲染系統正常地和其它內容組成這個View。默認值是YES。

在相對比較簡單的佈局中,設置這個屬性不會有太大影響。然而當這個view嵌在scroll view裏邊,或者是一個複雜動畫的一部分,不設置這個屬性的話會在很大程度上影響app的性能。

4.避免過於龐大的XIB

iOS5中加入的Storyboards正在快速取代XIB。然而XIB在一些場景中仍然頗有用。好比你的app須要適應iOS5以前的設備,或者你有一個自定義的可重用的view,你就不可避免地要用到他們。

若是你不得不XIB的話,使他們儘可能簡單。嘗試爲每一個Controller配置一個單獨的XIB,儘量把一個View Controller的view層次結構分散到單獨的XIB中去。

須要注意的是,當你加載一個XIB的時候全部內容都被放在了內存裏,包括任何圖片。若是有一個不會即刻用到的view,你這就是在浪費寶貴的內存資源了。然而storyboard僅在須要時實例化一個view controller.

在使用XIB,全部圖片都被chache,若是你在作OS X開發的話,聲音文件也是。

5.不要阻塞主線程

永遠不要使主線程承擔過多。由於UIKit在主線程上作全部工做,渲染,管理觸摸反應,迴應輸入等都須要在它上面完成。一直使用主線程的風險就是若是你的代碼真的block了主線程,你的app會失去反應。大部分阻礙主進程的情形是你的app在作一些牽涉到讀寫外部資源的I/O操做,好比存儲或者網絡。

你可使用NSURLConnection異步地作網絡操做:

*   (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue*)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler

或者使用像AFNetworking這樣的框架來異步地作這些操做。

若是你須要作其它類型的須要耗費巨大資源的操做(好比時間敏感的計算或者存儲讀寫)那就用 Grand Central Dispatch,或者NSOperation和 NSOperationQueues.

下面代碼是使用GCD的模板

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

// switch to a background thread and perform your expensive operation

dispatch_async(dispatch_get_main_queue(), ^{

// switch back to the main thread to update your UI

});

});

發現代碼中有一個嵌套的dispatch_async嗎?這是由於任何UIKit相關的代碼須要在主線程上進行。

6. 在Image Views中調整圖片大小

若是要在UIImageView中顯示一個來自bundle的圖片,你應保證圖片的大小和UIImageView的大小相同。在運行中縮放圖片是很耗費資源的,特別是UIImageView嵌套在UIScrollView中的狀況下。

若是圖片是從遠端服務加載的你不能控制圖片大小,好比在下載前調整到合適大小的話,你能夠在下載完成後,最好是用background thread,縮放一次,而後在UIImageView中使用縮放後的圖片。

這是個人iOS開發交流羣:519832104無論你是小白仍是大牛歡迎入駐,能夠一塊兒分享經驗,討論技術,共同窗習成長!

另附上一份各好友收集的大廠面試題,須要iOS開發學習資料、面試真題,進羣便可自行下載!

iOS開發——內存優化

點擊此處,當即與iOS大牛交流學習

7. 選擇正確的使用Collection

學會選擇對業務場景最合適的類或者對象是寫出能效高的代碼的基礎。當處理collections時這句話尤爲正確。

一些常見collection的總結:

· Arrays: 有序的一組值。使用index來lookup很快,使用value lookup很慢,插入/刪除很慢。

· Dictionaries: 存儲鍵值對。用鍵來查找比較快。

· Sets: 無序的一組值。用值來查找很快,插入/刪除很快。

8. 重用和延遲加載(lazy load) Views

更多的view意味着更多的渲染,也就是更多的CPU和內存消耗,對於那種嵌套了不少view在UIScrollView裏邊的app更是如此。

這裏咱們用到的技巧就是模仿UITableViewUICollectionView的操做:不要一次建立全部的subview,而是當須要時才建立,當它們完成了使命,把他們放進一個可重用的隊列中。

這樣的話你就只須要在滾動發生時建立你的views,避免了沒必要要的內存分配。

建立views的能效問題也適用於你app的其它方面。想象一下,一個用戶點擊一個按鈕的時候須要呈現一個view的場景。有兩種實現方法:

1. 建立並隱藏這個view當這個screen加載的時候,當須要時顯示它;

2. 當須要時才建立並展現。

每一個方案都有其優缺點。用第一種方案的話由於你須要一開始就建立一個view並保持它直到再也不使用,這就會更加消耗內存。然而這也會使你的app操做更敏感由於當用戶點擊按鈕的時候它只須要改變一下這個view的可見性。

第二種方案則相反-消耗更少內存,可是會在點擊按鈕的時候比第一種稍顯卡頓。

9. 避免日期格式轉換

若是你要用NSDateFormatter來處理不少日期格式。就像先前提到的,任什麼時候候重用NSDateFormatters都是一個好的實踐。

若是你能夠控制你所處理的日期格式,儘可能選擇Unix時間戳。你能夠方便地從時間戳轉換到NSDate:

*   (NSDate*)dateFromUnixTimestamp:(NSTimeInterval)timestamp {

return[NSDate dateWithTimeIntervalSince1970:timestamp];

}

10.渲染方法

在iOS中能夠有不少方法作出漂亮的按鈕。固然每一個不一樣的解決方法都有不一樣的複雜程度和相應的性能。簡單來講,就是用事先渲染好的圖片更快一些,由於如此一來iOS就免去了建立一個圖片再畫東西上去而後顯示在屏幕上的程序。問題是你須要把全部你須要用到的圖片放到app的bundle裏面,這樣就增長了體積–這就是使用可變大小的圖片更好的地方了:你能夠省去一些沒必要要的空間,也不須要再爲不一樣的元素(好比按鈕)來作不一樣的圖。

然而,使用圖片也意味着你失去了使用代碼調整圖片的機動性,你須要一遍又一遍不斷地重作他們,這樣就很浪費時間了,並且你若是要作一個動畫效果,雖然每幅圖只是一些細節的變化你就須要不少的圖片形成bundle大小的不斷增大。

總得來講,你須要權衡一下利弊,究竟是要性能能仍是要bundle保持合適的大小。

11.處理內存警告

一旦系統內存太低,iOS會通知全部運行中app。在官方文檔中是這樣記述:

若是你的app收到了內存警告,它就須要儘量釋放更多的內存。最佳方式是移除對緩存,圖片object和其餘一些能夠重建立的objects的strong references.

UIKit提供了幾種收集低內存警告的方法:

· 在app delegate中使用applicationDidReceiveMemoryWarning:的方法

· 在你的自定義UIViewController的子類(subclass)中覆蓋didReceiveMemoryWarning

· 註冊並接收 UIApplicationDidReceiveMemoryWarningNotification的通知

一旦收到這類通知,你就須要釋聽任何沒必要要的內存使用。

例如,UIViewController的默認行爲是移除一些不可見的view,它的一些子類則能夠補充這個方法,刪掉一些額外的數據結構。一個有圖片緩存的app能夠移除不在屏幕上顯示的圖片。

這樣對內存警報的處理是很必要的,若不重視,你的app就可能被系統殺掉。

然而,當你必定要確認你所選擇的object是能夠被重現建立的來釋放內存。必定要在開發中用模擬器中的內存提醒模擬去測試一下。

12.避免反覆處理數據

許多應用須要從服務器加載功能所需的常爲JSON或者XML格式的數據。在服務器端和客戶端使用相同的數據結構很重要。在內存中操做數據使它們知足你的數據結構是開銷很大的。

好比你須要數據來展現一個table view,最好直接從服務器取array結構的數據以免額外的中間數據結構改變。

相似的,若是須要從特定key中取數據,那麼就使用鍵值對的dictionary。

13.選擇正確的數據格式

從app和網絡服務間傳輸數據有不少方案,最多見的就是JSON和XML。你須要選擇對你的app來講最合適的一個。

解析JSON會比XML更快一些,JSON也一般更小更便於傳輸。從iOS5起有了官方內建的JSON deserialization就更加方便使用了。

可是XML也有XML的好處,好比使用SAX來解析XML就像解析本地文件同樣,你不需像解析json同樣等到整個文檔下載完成纔開始解析。當你處理很大的數據的時候就會極大地減低內存消耗和增長性能。

14.正確設定背景圖片

在View裏放背景圖片就像不少其它iOS編程同樣有不少方法:

使用UIColor的 colorWithPatternImage來設置背景色;

在view中添加一個UIImageView做爲一個子View。

若是你使用全畫幅的背景圖,你就必須使用UIImageView由於UIColor的colorWithPatternImage是用來建立小的重複的圖片做爲背景的。這種情形下使用UIImageView能夠節約很多的內存:

UIImageView *backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"background"]];

[self.view addSubview:backgroundView];

若是你用小圖平鋪來建立背景,你就須要用UIColor的colorWithPatternImage來作了,它會更快地渲染也不會花費不少內存:

self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"background"]];

15. 減小使用Web特性

UIWebView頗有用,用它來展現網頁內容或者建立UIKit很難作到的動畫效果是很簡單的一件事。

可是你可能有注意到UIWebView並不像驅動Safari的那麼快。這是因爲以JIT compilation爲特點的Webkit的Nitro Engine的限制。

因此想要更高的性能你就要調整下你的HTML了。第一件要作的事就是儘量移除沒必要要的javascript,避免使用過大的框架。能只用原生js就更好了。

另外,儘量異步加載例如用戶行爲統計script這種不影響頁面表達的javascript。

最後,永遠要注意你使用的圖片,保證圖片的符合你使用的大小。使用Sprite sheet提升加載速度和節約內存。

16. 優化Table View

Table view須要有很好的滾動性能,否則用戶會在滾動過程當中發現動畫的瑕疵。

爲了保證table view平滑滾動,確保你採起了如下的措施:

· 正確使用reuseIdentifier來重用cells

· 儘可能使全部的view opaque,包括cell自身

· 避免漸變,圖片縮放

· 緩存行高

· 若是cell內現實的內容來自web,使用異步加載,緩存請求結果

· 使用shadowPath來畫陰影

· 減小subviews的數量

· 儘可能不適用cellForRowAtIndexPath:,若是你須要用到它,只用一次而後緩存結果

· 使用正確的數據結構來存儲數據

· 使用rowHeight, sectionFooterHeightsectionHeaderHeight來設定固定的高,不要請求delegate

17. 使用Autorelease Pool

NSAutoreleasePool負責釋放block中的autoreleased objects。通常狀況下它會自動被UIKit調用。可是有些情況下你也須要手動去建立它。

假如你建立不少臨時對象,你會發現內存一直在減小直到這些對象被release的時候。這是由於只有當UIKit用光了autorelease pool的時候memory纔會被釋放。你能夠在你本身的@autoreleasepool裏建立臨時的對象來避免這個行爲:

NSArray *urls = <# An array of file URLs #>;

for(NSURL *url in urls) {

@autoreleasepool {

NSError *error;

NSString *fileContents = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];

/* Process the string, creating and autoreleasing more objects. */

}

}

這段代碼在每次遍歷後釋放全部autorelease對象

18. 選擇是否緩存圖片

常見的從bundle中加載圖片的方式有兩種,一個是用imageNamed,二是用imageWithContentsOfFile

既然有兩種相似的方法來實現相同的目的,那麼他們之間的差異是什麼呢?

imageNamed的優勢是當加載時會緩存圖片。imageNamed的文檔中這麼說:這個方法用一個指定的名字在系統緩存中查找並返回一個圖片對象若是它存在的話。若是緩存中沒有找到相應的圖片,這個方法從指定的文檔中加載而後緩存並返回這個對象。

相反的,imageWithContentsOfFile僅加載圖片。

下面的代碼說明了這兩種方法的用法:

UIImage *img = [UIImage imageNamed:@"myImage"];// caching

// or

UIImage *img = [UIImage imageWithContentsOfFile:@"myImage"];// no caching

那麼咱們應該如何選擇呢?

若是你要加載一個大圖片並且是一次性使用,那麼就不必緩存這個圖片,用imageWithContentsOfFile,這樣不會浪費內存來緩存它。

然而,在圖片反覆重用的狀況下imageNamed是一個好得多的選擇。

這是個人iOS開發交流羣:519832104無論你是小白仍是大牛歡迎入駐,能夠一塊兒分享經驗,討論技術,共同窗習成長!

另附上一份各好友收集的大廠面試題,須要iOS開發學習資料、面試真題,進羣便可自行下載!

iOS開發——內存優化

點擊此處,當即與iOS大牛交流學習

相關文章
相關標籤/搜索