1. 程式人生 > >IOS開發-提升app性能的25條建議和技巧

IOS開發-提升app性能的25條建議和技巧

contents oar profile 討論 遊戲開發 when plist 數據庫 formats

前言

這篇文章介紹了作者開發工作中總結的25個iOS開發tips, 多年之前讀過這篇文章。收益良多,基本每一個tips在我的應用開發過程中都使用過。今天把這篇文章又一次整理轉發下,與大家一起學習,不論你處於什麽開發階段,這些tips都值得你重復熟悉。

過一段時間。自己以也計劃翻譯些國外的技術文章,把國外開發人員的知識與國內開發人員分享,同一時候歡迎很多其它的開發人員加入我們:
https://github.com/bboyfeiyu/iOS-tech-frontier

介紹

本文來自iOS Tutorial Team 的 Marcelo Fabri。他是Movile的一名 iOS 程序猿。

這是他的個人站點:http://www.marcelofabri.com/,你還能夠在Twitter上關註@marcelofabri_。

性能對 iOS 應用的開發尤其重要,假設你的應用失去反應或者非常慢,失望的用戶會把他們的失望寫滿App Store的評論。然而由於iOS設備的限制。有時搞好性能是一件難事。開發過程中你會有非常多須要註意的事項,你也非常easy在做出選擇時忘記考慮性能影響。

這正是我寫下這篇文章的原因。

這篇文章以一個方便查看的核對表的形式整合了你能夠用來提升你app性能的25條建議和技巧。

請耐心讀完這篇文章。為你未來的app提個速。

註意:每在優化代碼之前,你都要註意一個問題。不要養成”預優化”代碼的錯誤習慣。時常使用Instruments去profile你的代碼來發現須要提升的方面。

Matt Galloway寫過一篇非常棒的怎樣利用Instruments來優化代碼的文章。

還要註意的是,這裏列出的當中一些建議是有代價的,所建議的方式會提升app的速度或者使它更加高效。但也可能須要花非常多功夫去應用或者使代碼變得更加復雜。所以要細致選擇。

1. 用ARC管理內存

ARC(Automatic Reference Counting, 自己主動引用計數)和iOS5一起公布,它避免了最常見的也就是常常是由於我們忘記釋放內存所造成的內存泄露。它自己主動為你管理retain和release的過程。所以你就不必去手動幹預了。

以下是你會常常常使用來去創建一個View的代碼段:

UIView *view = [[UIView alloc] init];

// ...
[self.view addSubview:view];
[view release];

忘掉代碼段結尾的release簡直像記得吃飯一樣簡單。

而ARC會自己主動在底層為你做這些工作。

除了幫你避免內存泄露,ARC還能夠幫你提高性能,它能保證釋放掉不再須要的對象的內存。這都啥年代了。你應該在你的全部項目裏使用ARC!

博主發現非常多公司,由於項目開發的時間比較早期。還沒有ARC, 使用的是MRC, 後期的不斷擴展中由於架構的問題的考慮。沒有使用ARC,這樣的現象還是有一些的。

從長遠考慮,趕快換上ARC吧!

ARC當然不能為你排除全部內存泄露的可能性。

由於堵塞, retain 周期, 管理不完好的CoreFoundation object(還有C結構)或者就是代碼太爛依舊能導致內存泄露。

這裏有一篇非常棒的介紹ARC不能做到以及我們該怎麽做的文章 http://conradstoll.com/blog/2013/1/19/blocks-operations-and-retain-cycles.html。

2. 在正確的地方使用 reuseIdentifier

一個開發中常見的錯誤就是沒有給UITableViewCells, UICollectionViewCells(這兩貨的使用像極了。),甚至是UITableViewHeaderFooterViews設置正確的reuseIdentifier。

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

不使用reuseIdentifier的話,每顯示一行table view就不得不設置全新的cell。這對性能的影響可是相當大的,尤其會使app的滾動體驗大打折扣。

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

想要使用reuseIdentifiers的話,在一個table view中加入一個新的cell時在data source object中加入這種方法:

//可重用cell標識, ReuseCell要與cell的indentifier同樣
static NSString *CellIdentifier = @"ReuseCell";

//在緩沖池中調用可重用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的性能。

你能夠在模擬器中用Debug\Color Blended Layers選項來發現哪些view沒有被設置為opaque。

目標就是。能設為opaque的就全設為opaque!

4. 避免過於龐大的XIB

iOS5中加入的Storyboards(分鏡)正在高速代替XIB。然而XIB在一些場景中仍然非常實用。比方你的app須要適應iOS5之前的設備。或者你有一個自己定義的可重用的view,你就不可避免地要用到他們。

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

須要註意的是,當你載入一個XIB的時候全部內容都被放在了內存裏,包含不論什麽圖片。假設有一個不會即刻用到的view,你這就是在浪費寶貴的內存資源了。Storyboards就是還有一碼事兒了,storyboard僅在須要時實例化一個view controller.

當載入XIB時,全部圖片都被chache。假設你在做OS X開發的話。聲音文件也是。Apple在相關文檔中的記述是:

當你載入一個引用了圖片或者聲音資源的nib時,nib載入代碼會把圖片和聲音文件寫進內存。

在OS X中,圖片和聲音資源被緩存在named cache中以便將來用到時獲取。

在iOS中,僅圖片資源會被存進named caches。

取決於你所在的平臺,使用NSImage 或UIImage 的imageNamed:方法來獲取圖片資源。

非常明顯,同樣的事情也發生在storyboards中,但我並沒有找到不論什麽支持這個結論的文檔。

假設你了解這個操作,寫信給我!

想要了解很多其它關於storyboards的內容的話你能夠看看 Matthijs Hollemans的Beginning Storyboards in iOS 5 Part 1 和 Part 2

5. 不要堵塞主線程

永遠不要使主線程承擔過多。由於UIKit在主線程上做全部工作,渲染,管理觸摸反應,回應輸入等都須要在它上面完畢。

一直使用主線程的風險就是假設你的代碼真的block了主線程。你的app會失去反應。這正是在App Store中拿到一顆星的捷徑 :]

大部分阻礙主進程的情形是你的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), ^{

    //切換到子線程異步執行一些開銷比較大的操作

    dispatch_async(dispatch_get_main_queue(), ^{
    //切換到主線程,更新UI等操作

    });
});

發現代碼中有一個嵌套的dispatch_async嗎?

這是由於不論什麽UIKit相關的代碼須要在主線程上進行。

假設你對 NSOperation 或者GCD 的細節感興趣的話。看看Ray Wenderlich的 Multithreading and Grand Central Dispatch on iOS for Beginners。 還有 Soheil Azarpour 的 How To Use NSOperations and NSOperationQueues 教程。

6. 在ImageView中調整圖片大小

假設要在UIImageView中顯示一個來自bundle的圖片,你應保證圖片的大小和UIImageView的大小同樣。

在執行中縮放圖片是非常耗費資源的,特別是UIImageView嵌套在UIScrollView中的情況下。

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

7. 選擇正確的Collection

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

Apple有一個 Collections Programming Topics的文檔詳盡介紹了可用的classes間的區別和你該在哪些場景中使用它們。這對於不論什麽使用collections的人來說是一個必讀的文檔。

呵呵。我就知道你由於太長沒看…這是一些常見collection的總結:

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


Dictionaries: 存儲鍵值對。 用鍵來查找比較快。
Sets: 無序的一組值。用值來查找非常快。插入/刪除非常快。

8. 打開gzip壓縮

大量app依賴於遠端資源和第三方API,你可能會開發一個須要從遠端下載XML, JSON, HTML或者其它格式的app。

問題是我們的目標是移動設備。因此你就不能指望網絡狀況有多好。一個用戶如今還在edge網絡。下一分鐘可能就切換到了3G。不論什麽場景,你肯定不想讓你的用戶等太長時間。

減小文檔的一個方式就是在服務端和你的app中打開gzip。這對於文字這樣的能有更高壓縮率的數據來說會有更顯著的效用。

好消息是,iOS已經在NSURLConnection中默認支持了gzip壓縮,當然AFNetworking這些基於它的框架亦然。

像Google App Engine這些雲服務提供者也已經支持了壓縮輸出。

假設你不知道怎樣利用Apache或者IIS(server)來打開gzip,能夠讀下這篇文章。

9. 重用和延遲載入(lazy load) Views

很多其它的view意味著很多其它的渲染,也就是很多其它的CPU和內存消耗,對於那種嵌套了非常多view在UIScrollView裏邊的app更是如此。

這裏我們用到的技巧就是模仿UITableViewUICollectionView的操作:

不要一次創建全部的subview,而是當須要時才創建,當它們完畢了使命。把他們放進一個可重用的隊列中。

這樣的話你就僅僅須要在滾動發生時創建你的views。避免了不劃算的內存分配。

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

  1. 創建並隱藏這個view當這個screen載入的時候。當須要時顯示它;
  2. 當須要時才創建並展示。

每一個方案都有其優缺點。

  • 用第一種方案的話由於你須要一開始就創建一個view並保持它直到不再使用,這就會更加消耗內存。然而這也會使你的app操作更敏感由於當用戶點擊button的時候它僅僅須要改變一下這個view的可見性。

  • 另外一種方案則相反-消耗更少內存。可是會在點擊button的時候比第一種稍顯卡頓。

10. Cache, Cache, 還是Cache!

一個極好的原則就是,緩存所須要的,也就是那些不大可能改變可是須要常常讀取的東西。

我們能緩存些什麽呢?一些選項是:

遠端server的響應,圖片。甚至計算結果。比方UITableView的行高。

NSURLConnection會依據它所載入的HTTP Headers默認緩存資源在內存或者存儲中。你甚至能夠手動創建一個NSURLRequest然後使它僅僅載入緩存的值。

以下是一個可用的代碼段,你能夠用它去為一個基本不會改變的圖片創建一個NSURLRequest並緩存它:

+ (NSMutableURLRequest *)imageRequestWithURL:(NSURL *)url
{
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

    //加入請求的緩存策略,保證每次都是優先載入緩存圖片
    request.cachePolicy = NSURLRequestReturnCacheDataElseLoad; 

    request.HTTPShouldHandleCookies = NO;
    request.HTTPShouldUsePipelining = YES;
    [request addValue:@"image/*" forHTTPHeaderField:@"Accept"];

    return request;
}

註意你能夠通過 NSURLConnection 獲取一個URL request, AFNetworking也一樣的。

這樣你就不必為採用這條tip而改變全部的networking代碼了。

假設你須要緩存其它不是HTTP Request的東西。你能夠用NSCache。

  • NSCache和NSDictionary相似。不同的是系統回收內存的時候它會自己主動刪掉它的內容。

    Mattt Thompson有一篇非常棒的關於它的文章::http://nshipster.com/nscache/

  • 假設你對HTTP感興趣能夠讀下Google的這篇 best-practices document on HTTP caching。

11. 權衡渲染方法

在iOS中能夠有非常多方法做出美麗的button。你能夠用整幅的圖片,可調大小的圖片,uozhe能夠用CALayer, CoreGraphics甚至OpenGL來畫它們。

當然每一個不同的解決方法都有不同的復雜程度和對應的性能。有一篇Apple UIKit team中的一員Andy Matuschak推薦過的非常棒的關於graphic性能的帖子非常值得一讀。

簡單來說。就是用事先渲染好的圖片更快一些,由於如此一來iOS就免去了創建一個圖片再畫東西上去然後顯示在屏幕上的程序。

問題是你須要把全部你須要用到的圖片放到app的bundle裏面,這樣就添加了體積 。

這就是使用可變大小的圖片更好的地方了: 你能夠省去一些不必要的空間,也不須要再為不同的元素(比方button)來做不同的圖。

然而,使用圖片也意味著你失去了使用代碼調整圖片的機動性。你須要一遍又一遍不斷地重做他們,這樣就非常浪費時間了,並且你假設要做一個動畫效果,盡管每幅圖僅僅是一些細節的變化你就須要非常多的圖片造成bundle大小的不斷增大。

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

12. 處理內存警告

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

假設你的app收到了內存警告,它就須要盡可能釋放很多其它的內存。最佳方式是移除對緩存,圖片object和其它一些能夠重創建的objects的strong references.

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

在app delegate中使用applicationDidReceiveMemoryWarning:
的方法在你的自己定義UIViewController的子類(subclass)中覆蓋didReceiveMemoryWarning
註冊並接收 UIApplicationDidReceiveMemoryWarningNotification 的通知
的對象一旦收到這類通知,你就須要釋放不論什麽不必要的內存使用。

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

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

然而,當你一定要確認你所選擇的object是能夠被重現創建的來釋放內存。

一定要在開發中用模擬器中的內存提醒模擬去測試一下。

13. 重用大開銷對象

一些objects的初始化非常慢,比方NSDateFormatterNSCalendar。然而。你又不可避免地須要使用它們。比方從JSON或者XML中解析數據

想要避免使用這個對象的瓶頸你就須要重用他們。能夠通過加入屬性到你的class裏或者創建靜態變量來實現。

註意假設你要選擇另外一種方法,對象會在你的app執行時一直存在於內存中,和單例(singleton)非常相似。

以下的代碼說明了使用一個屬性來延遲載入一個date formatter. 第一次調用時它會創建一個新的實例,以後的調用則將返回已經創建的實例:

// 懶載入!
@property (nonatomic, strong) NSDateFormatter *formatter;

// inside the implementation (.m)
// When you need, just use self.formatter
- (NSDateFormatter *)formatter {
    if (! _formatter) {
        _formatter = [[NSDateFormatter alloc] init];
        _formatter.dateFormat = @"EEE MMM dd HH:mm:ss Z yyyy"; 
// twitter date format
    }
    return _formatter;
}

還須要註意的是,事實上設置一個NSDateFormatter的速度幾乎相同是和創建新的一樣慢的!所以假設你的app須要常常進行日期格式處理的話,你會從這種方法中得到不小的性能提升。

14. 使用Sprite Sheets

你是一個遊戲開發人員嗎,那麽Sprite sheets一定是一個你的最好的朋友了。Sprite sheet能夠讓渲染速度加快。甚至比標準的屏幕渲染方法節省內存。

我們有兩個非常好的關於Sprite的教程:

How To Use Animations and Sprite Sheets in Cocos2D
How to Create and Optimize Sprite Sheets in Cocos2D with Texture Packer and Pixel Formats

第二個教程涵蓋了可能在非常大程度上影響你遊戲性能的pixel格式的細節。

假設你對於spirte sheet還不是非常熟悉,能夠看下這兩個(youtube)視頻SpriteSheets – The Movie, Part 1 和 Part 2。

視頻的作者是創建Sprite sheet非常流行的工具之中的一個Texture Packer的作者Andreas L?w。

除了使用Sprite sheets。其它寫在這裏的建議當然也能夠用於遊戲開發中。比方你須要非常多的Sprite sheets,像敵人。導彈之類的動作類必備元素。你能夠重用這些sprites而不用每次都要又一次創建。

15. 避免重復處理數據

很多應用須要從server載入功能所需的常為JSON或者XML格式的數據。

在server端和client使用同樣的數據結構非常重要。

在內存中操作數據使它們滿足你的數據結構是開銷非常大的。

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

相似的,假設須要從特定key中取數據,那麽就使用鍵值對的dictionary。

16. 選擇正確的數據格式

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

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

可是XML也有XML的優點,比方使用SAX 來解析XML就像解析本地文件一樣,你不需像解析json一樣等到整個文檔下載完畢才開始解析。當你處理非常大的數據的時候就會極大地減低內存消耗和添加性能。

17. 正確設定背景圖片

在View裏放背景圖片就像非常多其它iOS編程一樣有非常多方法:

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

在view中加入一個UIImageView作為一個子View。

假設你使用全畫幅的背景圖,你就必須使用UIImageView。

由於UIColor的colorWithPatternImage是用來創建小的重復的圖片作為背景的。這樣的情形下使用UIImageView能夠節約不少的內存:

//You could also achieve the same result in Interface Builder

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

[self.view addSubview:backgroundView];

假設你用小圖平鋪來創建背景。你就須要用UIColor的colorWithPatternImage來做了,它會更快地渲染也不會花費非常多內存:

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

18. 降低使用Web特性

UIWebView非常實用,用它來展示網頁內容或者創建UIKit非常難做到的動畫效果是非常easy的一件事。

可是你可能有註意到UIWebView並不像驅動Safari的那麽快。這是由於以JIT compilation 為特色的Webkit的Nitro Engine的限制。

所以想要更高的性能你就要調整下你的HTML了。第一件要做的事就是盡可能移除不必要的javascript,避免使用過大的框架。

能僅僅用原生js就更好了。

另外,盡可能異步載入比如用戶行為統計script這樣的不影響頁面表達的javascript。

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

很多其它相關信息能夠看下 WWDC 2012 session #601 – Optimizing Web Content in UIWebViews and Websites on iOS

19. 設定Shadow Path

怎樣在一個View或者一個layer上加一個shadow呢。QuartzCore框架是非常多開發人員的選擇:

#import <QuartzCore/QuartzCore.h>

// Somewhere later ...
UIView *view = [[UIView alloc] init];

// Setup the shadow ...
view.layer.shadowOffset = CGSizeMake(-1.0f, 1.0f);
view.layer.shadowRadius = 5.0f;
view.layer.shadowOpacity = 0.6;

看起來非常easy,對吧。可是,壞消息是使用這種方法也有它的問題

Core Animation不得不先在後臺得出你的圖形並加好陰影然後才渲染。這開銷是非常大的。

使用shadowPath的話就避免了這個問題:

view.layer.shadowPath = [[UIBezierPath bezierPathWithRect:view.bounds] CGPath];

使用shadow path的話iOS就不必每次都計算怎樣渲染,它使用一個預先計算好的路徑。

但問題是自己計算path的話可能在某些View中比較困難,且每當view的frame變化的時候你都須要去update shadow path.

想了解很多其它能夠看看Mark Pospesel的這篇。

20. 優化Table View

Table view須要有非常好的滾動性能。不然用戶會在滾動過程中發現動畫的瑕疵。

為了保證table view平滑滾動,確保你採取了以下的措施:

  • 正確使用reuseIdentifier來重用cells
  • 盡量使全部的view opaque。包含cell自身
  • 避免漸變,圖片縮放,後臺選人
  • 緩存行高
  • 假設cell內現實的內容來自web,使用異步載入,緩存請求結果
  • 使用shadowPath來畫陰影
  • 降低subviews的數量
  • 盡量不適用cellForRowAtIndexPath:,假設你須要用到它,僅僅用一次然後緩存結果
  • 使用正確的數據結構來存儲數據
  • 使用rowHeight, sectionFooterHeightsectionHeaderHeight來設定固定的高。不要請求delegate

21. 選擇正確的數據存儲選項

當存儲大塊數據時你會怎麽做?

你有非常多選擇,比方:

  • 使用NSUerDefaults
  • 使用XML, JSON, 或者 plist
  • 使用NSCoding存檔
  • 使用相似SQLite的本地SQL數據庫
  • 使用 Core Data

NSUserDefaults的問題是什麽?盡管它非常nice也非常便捷。可是它僅僅適用於小數據,比方一些簡單的布爾型的設置選項。再大點你就要考慮其它方式了

XML這樣的結構化檔案呢?整體來說,你須要讀取整個文件到內存裏去解析。這樣是非常不經濟的。使用SAX又是一個非常麻煩的事情

NSCoding?不幸的是。它也須要讀寫文件,所以也有以上問題。

在這樣的應用場景下。使用SQLite 或者 Core Data比較好。使用這些技術你用特定的查詢語句就能載入你須要的對象。

在性能層面來講,SQLite和Core Data是非常相似的。

他們的不同在於詳細用法。Core Data代表一個對象的graph model。但SQLite就是一個DBMS。Apple在普通情況下建議使用Core Data,可是假設你有理由不使用它。那麽就去使用更加底層的SQLite吧。

假設你使用SQLite。你能夠用FMDB(https://github.com/ccgus/fmdb)這個庫來簡化SQLite的操作,這樣你就不用花非常多經歷了解SQLite的C API了。

22. 加速啟動時間

高速打開app是非常重要的。特別是用戶第一次打開它時。對app來講,第一印象太太太重要了。

你能做的就是使它盡可能做很多其它的異步任務。比方載入遠端或者數據庫數據,解析數據。

還是那句話。避免過於龐大的XIB。由於他們是在主線程上載入的。

所以盡量使用沒有這個問題的Storyboards吧!

註意,用Xcode debug時watchdog並不執行。一定要把設備從Xcode斷開來測試啟動速度

23. 使用Autorelease Pool

NSAutoreleasePool負責釋放代碼塊中的autoreleased objects。

普通情況下它會自己主動被UIKit調用。

可是有些狀況下你也須要手動去創建它。

假如你創建非常多暫時對象,你會發現內存一直在降低直到這些對象被release的時候。

好消息是你能夠在你自己的@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對象

很多其它關於NSAutoreleasePool請參考官方文檔。

24. 選擇是否緩存圖片

常見的從bundle中載入圖片的方式有兩種。一個是用imageNamed,二是用imageWithContentsOfFile,第一種比較常見一點。

既然有兩種相似的方法來實現同樣的目的,那麽他們之間的區別是什麽呢?

imageNamed的優點是當載入時會緩存圖片。imageNamed的文檔中這麽說:
這種方法用一個指定的名字在系統緩存中查找並返回一個圖片對象假設它存在的話。假設緩存中沒有找到對應的圖片,這種方法從指定的文檔中載入然後緩存並返回這個對象。

相反的,imageWithContentsOfFile僅載入圖片。

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

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

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

那麽我們應該怎樣選擇呢?

假設你要載入一個大圖片並且是一次性使用,那麽就不是必需緩存這個圖片,用imageWithContentsOfFile足矣。這樣不會浪費內存來緩存它。

然而。在圖片重復重用的情況下imageNamed是一個好得多的選擇。

25. 避免日期格式轉換

假設你要用NSDateFormatter來處理非常多日期格式。應該小心以待。就像先前提到的,不論什麽時候重用NSDateFormatters都是一個好的實踐。

然而,假設你須要很多其它速度,那麽直接用C是一個好的方案。

Sam Soffes有一個不錯的帖子(http://soff.es/how-to-drastically-improve-your-app-with-an-afternoon-and-instruments)裏面有一些能夠用來解析ISO-8601日期字符串的代碼,簡單重寫一下就能夠拿來用了。

嗯,直接用C來搞,看起來不錯了,可是你相信嗎,我們還有更好的方案!

假設你能夠控制你所處理的日期格式,盡量選擇Unix時間戳。你能夠方便地從時間戳轉換到NSDate:

- (NSDate*)dateFromUnixTimestamp:(NSTimeInterval)timestamp
{
    return [NSDate dateWithTimeIntervalSince1970:timestamp];
}

這樣會比用C來解析日期字符串還快!

須要註意的是,很多web API會以微秒的形式返回時間戳,由於這樣的格式在javascript中更方便使用。

記住用dateFromUnixTimestamp之前除以1000就好了。

後記

這篇博文到這裏就要結束了,歡迎博友們在評論中討論自己在開發中遇到了一些問題。

IOS開發-提升app性能的25條建議和技巧