[CS193P] 第十堂課摘要及心得筆記

在歷經許多堂的開發課程後,大家對於iPhone開發已經算是相當熟練了。在這一次的課程內容中,我們將深入探討iPhone應用程式的效能問題,看看我們要如何透過iPhone SDK所提供的相關工具來調校我們的軟體,你準備好了嗎?
評論
評論

在歷經許多堂的開發課程後,大家對於 iPhone 開發已經算是相當熟練了。在這一次的課程內容中,我們將深入探討 iPhone 應用程式的效能問題,看看我們要如何透過 iPhone SDK 所提供的相關工具來調校我們的軟體,你準備好了嗎?

Lazy Loading

在 iPhone 中最容易影響程式要能的莫過於記憶體的管理了!在之前的每堂課程中,我們多少都有介紹一些關於記憶體上使用所需注意的細節。其中 Cocoa Touch 中最重要的精神莫過於 Lazy Loading 了,也就是只在真的需要資料時在做讀取。一個簡單的例子是,我們可以將多個使用者介面分別存放於不同的 Nib 檔,只在 View 被呼叫之後在讀取對應的 Nib,而非將所有使用者介面一起放到 MainWindow.xib 檔中。

此外,對於一些物件所擁有的變數,我們也可以透過實做自己的 Property 方法來進行 Lazy Loading,而非在 init 時就初始化變數。透過這樣的作法,不僅可以節省記憶體的使用,也可以加速讀取的時間。請參考以下程式碼:

- (UIImage *)myImage {     if (myImage == nil) {         myImage = [self readSomeHugeImageFromDisk];     } }

Memory Leak

Memory Leak 中文翻譯叫做記憶體漏洩,也就是某些物件在記憶體中沒有被釋放,反而留在記憶體中。在 Objetive-C 中,會發生這種問題往往都是因為沒有正確的平衡 release 和 retain 的呼叫,讓 retain count 數字無法正確的歸零。很幸運的是,在 iPhone SDK 中內建了一套相當容易使用的檢測工具,我們可以很方便的找到 Memory Leak。在 XCode 開啟檢測工具的方法如下圖所示:
而下面這個畫面就是檢測工具的執行畫面,檢測工具會打開 iPhone 模擬器並且執行我們的軟體,而同時間也在背後以 10 秒鐘為間隔幫我們對記憶體取樣並且找尋忘記釋放的物件。上面的曲線圖就顯示了目前總共漏失的記憶體大小,下方的列表則是所漏失的物件,而當我們點選下方的圖示開啟右邊的面板後,還會顯示物件的詳細資訊,也就是其中所經過的每一個方法呼叫。

Autorelease Pool

在一開始前幾次的筆記中我們曾經提過,我們可以對物件呼叫 autorelease 使其加入 autorelease pool,而當這個 pool 被 release 時,其中所有物件也會被呼叫 release。而透過這樣的機制,我們得以將物件從方法中回傳,而又可以減少其 retain count。

然而 autorelease pool 也並非萬能,因為在 Cocoa Touch 的機制中,每當使用者觸發一個 event 時就會建立一個新的 autorelease pool,並在 event 結束時 release 這個 pool。問題是,這個 pool 可能會在 event 中容納過多的物件,而在 event 結束前這些記憶體用量將會變得十分龐大。

為了解決這樣的問題,我們可以自行建立 pool,在有需要大量建立物件的地方用 pool 包起來,並且在使用後 release 該 pool。以下為範例程式:

for (int i = 0; i < someLargeNumber; i++) {     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];     NSString *string = ...;     string = [string lowercaseString];     string = [string stringByAppendingString:...];     NSLog(@“%@”, string);     [pool release]; }

根據講者說法,與其擔心 autorelease pool 的問題,他個人較偏好盡量減少使用 autorelease 的機制,像是以上面的程式碼而言,其實我們可以透過 NSMutableString 來達成一樣的效果。

此外,我們也可以透過 iPhone SDK 的附加工具來觀察記憶體的使用量,使用方式就如同剛剛所提到的 Memory Leak 檢查工具一般,有興趣的同學還請觀看課程影片中的示範。

記憶體警告

即便我們在如何的小心處理記憶體問題,我們仍然可能會面臨記憶體不足的困境,此外,由於使用者可能在背景同時播放音樂或是收信等等,記憶體的可用量是隨時會改變的。而為了保護 iPhone 能繼續運行,若應用程式的記憶體用量超過一定極限後,iPhone OS 便會強制終止程式的運行。

值得慶幸的是,iPhone OS 在中斷我們的程式執行之前,會對 controller 呼叫 - (void)didReceiveMemoryWarning 這個方法,提醒我們要適當的減少記憶體用量。這個方法預設會自動 release 那些暫時沒有顯示的 View,我們也以自行加入一些程式碼以 release 其他一些用不到的資源。

而 Application Delegate 也會收到 -applicationDidReceiveMemoryWarning:,我們同樣可以在這邊釋放一些記憶體。

多執行緒

目前我們所設計的應用程式大多為單一執行續的軟體,換而言之,當使用者的操作涉及了長時間的資料操作時,我們的使用者介面就會暫時卡住,使用者將暫時無法操作介面。而這樣的軟體設計將會是糟糕的,所以我們可以借重多執行緒來幫我們改善。

在 Cocoa Touch 中,Foundation framework 提供了一套以 POSIX 為基礎的多執行緒相關函式。以下就是一段簡單的範例:

- (void)someAction:(id)sender {    // 建立新的執行緒     [NSThread detachNewThreadSelector:@selector(doWork:) withTarget:self object:someData]; } - (void)doWork:(id)someData {     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];     [someData doLotsOfWork];     // 通知主執行緒     [self performSelectorOnMainThread:@selector(allDone:) withObject:[someData result] waitUntilDone:NO]; [pool release]; }

值得注意的是,針對每一個新建立的 NSThread,我們都需要自己加入 autorelease pool。

Locks

然而,在我們有了多執行緒的支援後,伴隨而來的就會是資料一致性的問題。針對某些程式碼,我們會希望他不會受到其他執行緒的干擾,我們就可以使用 locks。程式碼如下:

- (void)init { myLock = [[NSLock alloc] init]; }  - (void)someMethod { }     [myLock lock];     // 做某些事情     [myLock unlock]; }

此外,針對 producer/consumer 的 model,Cocoa 也有提供 condition lock 可以使用,有興趣的讀者還請參考投影片 41 頁的程式碼。

結論

在這次的內容中,我們學到了如何使用 iPhone SDK 中內建的工具來幫助我們調校應用程式的效能,也針對多執行緒以及其相關的類別做了一粗淺的介紹。在下一次的課程中,我們將探討如何使用文字輸入以及其他 UIKit 的類別,敬請期待!

參考資源


精選熱門好工作

客服專員 擴大徵才中

樂購蝦皮股份有限公司
臺北市.台灣

獎勵 NT$15,000

Data Analyst / Data Scientist

Omlet Arcade 美商歐姆雷特
臺北市.台灣

獎勵 NT$15,000

PopDaily 視覺設計師–【設計部】

數果網路股份有限公司
臺北市.台灣

獎勵 NT$15,000

評論