版權聲明

所有的部落格文章都可以在右邊[blog文章原始檔案]下載最原始的文字檔案,並依你高興使用 docutil 工具轉換成任何對應的格式方便離線閱覽,除了集結成書販賣歡迎任意取用,引用

iPhone Application Programming Guild (note1)

iPhone Application Programming Guild (note1)

簡介

Apple 官方 iPhone 手冊的 The Core Application 和 Application Runtime environment 章節閱讀筆記。

原文連結

The Core Application Objects

下圖非常清楚描繪了一個 iPhone 應用程式基本的架構和關鍵的物件。

UIApplication 負責收集 event 方便我們撰寫的物件處理,

http://s3.amazonaws.com/ember/TRwBbXoDTTzZG9mfMiYr39dTjeU37BRC_m.png
UIApplication:

關於 UIApplication 可以參考這篇有較為深入的解釋:

http://psvsps2.blogspot.com/2010/04/uiapplication-contents-application.html

Application delegate:

應用程式必須有一個物件其Class comform UIApplicationDelegate protocol, UIApplicationDelegate 物件主要任務為處理應用程式的狀態(state)通知,如應用程式 需要關閉(Terminate),完成啟動(FinishLaunching),進入背景(EnterBackground), 或是從背景被喚(EnterForeground)起等等,關於這些應用程式狀態文章後面會有比較詳細的說明。 UIApplicationDelegate 還必須負責啟動 window ( UIWindow )

[window makeKeyAndVisible];

更詳細的說明請參閱 The Application Delegate 章節。

Data model objects:
MVC 設計樣式中 Model 部分,負責維護 MVC 架構資料的部份,如通訊錄的個人資訊。
View controller objects:
MVC 設計樣式中 Controller 部分,負責Model和View之間的橋樑,滿足不同的 UIView 後代物件的需求。
UIWindow object:
負責 UIView 物件的管理與呈現到螢幕上,以及event配發。
Views:
UIView 後代物件,負責應用程式幾乎所有的顯示工作, cococ touch 定義許多十分 優秀好用的 view 供我們使用了。

The Application Life cycle

完整的生命週期就是按下程式圖示進入程式開始一直到離開程式為止。

http://s3.amazonaws.com/ember/UFTNeMynlIUroSkyirYPqNL18FZKtdjz_m.png

資深的 iPhone 開發者應該會十分在意 applicaitonDidEnterBackground: 這個 message, 正如你所想像這就是 iPhone OS 4.0 因應多工而增加的,文章後面會有較詳細的說明,這邊有個概念 應用程式進入不再是單一入口了,如果有必須在應用程式啟動即時更新的資訊可能不能想以往一樣全部 放在 application: didFinishLaunchingWithOptions: 了。

The Main Function

iPhone 的 main() 幾乎不需要特別的更改,main()會負責啟動一個 autorelease pool, 這東西就是 Objective-C 記憶體管理中送出 autorelease 訊息的物件會暫時存放的地方, 然後呼叫 UIApplicationMain(..) 來建立 UIApplication 物件。

int main(int argc, char *argv[]) {

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    int retVal = UIApplicationMain(argc, argv, nil, nil);
    [pool release];
    return retVal;
}

UIApplicationMain() 前兩個參數很單純就是將 main() 的啟動參數帶入, 第三個參數指定執行 iPhone 應用程式的 class 預設是 UIApplication ,第 四個參數設定實作 UIApplicationDelegate 的 class,預設 nil 代表 這個 class 會記錄在 nib file (xib file)。

The Application Delegate

每個 iPhone 程式都必須具備一個實現(comform) UIApplicationDelegate protocal 的物件 ,透過 UIApplicationDelegate protocal 定義的 delegates 來控制一些十分重要的 application event,像 applicationDidFinishLaunching: 代表系統告知已經完成 程式啟動步驟。

了解應用程式的狀態和狀態轉變

iPhone OS 4.0 針對多工有增加一些 OS 3.0 沒有的 Application states,這節簡單敘述一下

Not running
程式完全沒有執行乖乖躺在 Flash 的狀態。
Inactive
應用程式在前景(foreground)執行但是停止接收 event,通常是因為系統有其他任務必須立刻告知使用者, 如收到簡訊會將目前執行的應用程式強制進入 Inactive 並在螢幕上告知使用者等待使 用者回應 (按下OK)。
Active
應用程式在前景(foreground)執行,且可以正確接收所有events。
Background
應用程式在背景(background)執行,通常在應用程式被要求進入 suspend state 前會短暫的進入 background state 好讓應用程式可以有時間處理些收尾的動作,如記錄目前狀態等等。這是iPhone OS 4.0 新增的狀態,詳細說明可以參考本文 Executing Code in the Background 章節。
Suspended
應用程式在背景(background)但不繼續執行,應用程式此時依然留在主記憶體中,所以重新喚起 需要的資源較少,時間也較短。 如果記憶體不夠系統會在毫無任何通知下自行移除處於 suspended state 的應用程式,基本上由最久沒有回到Active state的應用程式開始移除。

Lauching the Application (啟動應用程式)

應用程式起始流程如下圖所示,第一步先讀取 main nib 並建立 conform UIApplicationDelegate protocol 物件,接下來對 applicaiton delegate object 發送 application: didFinishLaunchingWithOptions: 給予應用程式初始化額外資料的機會,然後依據應用程式的選擇進入前景(foreground)執行 或是背景(background)待命,並呼叫applicaion delegate object中對應的方法 applicaitonDidBecomActive or applicaitonDidEnterBackground

http://s3.amazonaws.com/ember/xmOudpUKtmXSelmkT3rOyqCDfscO1mEc_m.png

移到背景 (Background)

應用程式在使用者按下 hoem 鍵或是系統切換應用程式都會讓應用程式進入背景, 下圖示進入背景的流程和對應的application delegate方法,一般來說應用程式 處理完 applicationDidEnterBackground 後集會進入 suspend 狀態, 除非應用程式明確要求額外執行時間處理額外的工作。

applicaitonDidEnterBackground 被呼叫後應用程式預設情形下只有5秒 的時間處理,如果5秒內處理不完,應用程式會被關閉而無法進入背景(background) 。

由於系統在需要記憶體時會未經詢問直接移除背景的應用程式,你應該在 applicaitonDidEnterBackground 記錄一些重要不想遺漏的資料。(不論應用程式是否具備背景執行的額外能力都應該在 applicaitonDidEnterBackground 裡面處理儲存完重要資料 )

http://s3.amazonaws.com/ember/pABQ07ZfXUJrtS5njpQigbEZCgdhalWp_m.png

應用程式被移到背景後,所有應用程式裡面的物件都會被保存在記憶體,除了臨時的圖形資料 (沒有 retain 的圖檔資料) 和一些系統管理的資料快取。

更早的程式 (OS 3.0 or 更早)不會有移到背景的行為,應用程式必須處理 Termination 狀態,詳細資訊 請參考 回應 Application Termination

處理 interrupt

應用程式執行期間 (Active) 收到電話,簡訊或是calender 的通知時,我們說應用程式被 Interrupt ,此時應用程式會進入 Inactive 狀態等使用者回應 interrupt ,如果使用者忽略 interrupt 應用 程式會重回 Active 狀態,想反的如果使用者接受 interrupt 應用程式會進入 backgroupd 狀態。

http://s3.amazonaws.com/ember/KEPwvrA66bixVVKBhrGvheFH6L9CmgpG_m.png

應用程式可以在 applicationWillResignActive 中針對 interrupt 做必要的處理, 比如遊戲應該中斷等等。相對於 applicationWillResignActive 應用程式應該在 applicationDidBecomeActive 中回復應用程式的狀態。

回到 Foreground

參考下圖了解對應的 applicaiton delegate selector

http://s3.amazonaws.com/ember/YbplRcp7u587H4oIr0EObbfn9vjpO19p_m.png

回應 Application Termination

應用程式執行在OS 3.X 或更早,或是在 Info.plish 加入 UIApplicationExitsOnSuspend 不會進入背景,此時記得改用 -applicationWillTerminate: 來處理對應的工作。

The Main Nib File

如果程式的 Info.plist 檔案內容有 key 值 NSMainNibFile , 程式在啟動的時候會依據 NSMainNibFile 指示的檔案去讀取對應的 nib (xib [1])。

http://s3.amazonaws.com/ember/HaxYLTsQKKe2nlBYCpTShbMwWENUD3fe_m.png

一般基本的 main nib 應該有一個 UIApplicationDelegate 物件, 一個 window 物件, 每個 nib 檔案都會有 File's Owner 和 First Responder , 請參考下圖途中另外包含 一個基本 View control。

http://s3.amazonaws.com/ember/bhnbBikUK2XZAE3JRxeL7Le7Y0loYZg1_m.png
[1]xib 和 nib 是一樣的檔案,只是xib檔案用xml呈現內容。

The Event-Handling Cycle

看看下圖就很清楚事件的發生和流程。

手指觸碰 iPhone -> 作業系統收到 -> 作業系統將資訊包裝完畢後丟入 queue -> Application 從 queue 取出 event -> 將取出的 event 放入 application object -> application object 將 event 丟入 responder chain [2] 流程由上至下非常清楚。

http://s3.amazonaws.com/ember/rFJiKZSPT9b0EMYybcnRMJK7q0QMSBMn_m.png

上述模型中的 event 在 cocoa touch 定義了一個 UIEvent 類別專門描述這些 event 物件, 截至目前為止 cocoa touch 將 UIEvent 分成三種不同的型態:

  • touch event (下圖 Multitouch events)
  • motion event (下圖 Accelerometer events)
  • remote-control event (下圖 Remote-control events)
http://s3.amazonaws.com/ember/VWEW96CFEwc44a87V5m4YgPX5IqbfFE7_m.png

對於 Event 相關內容可以參考更詳細的 "Event Handling Guide for iPhone OS", 自行在 Google 或是 xcode 說明文件收詢及可。

在回到 iPhone event 模型上面,應用程式唯一的一個 UIApplication instance 有維護 一個主迴圈 (main looo),這迴圈會不斷從系統維護的 event queue 中取出 event,並包裝 成 UIEvent 物件傳遞給 First Responder, First Responder 就是 Application 中第一個接收 UIEvent 的物件,通常是 main window。

http://s3.amazonaws.com/ember/WsIz75HTEPfgn6RiPf6jlMarByoq35jO_m.png

如果 First Responder 不想處理收到的 UIEvent 物件,就直接丟去整個 Responer chain 中下一個 UIResponder 物件處理如果還是對傳來的 UIEvent 沒有興趣就繼續往下丟以此類推。

http://s3.amazonaws.com/ember/mUnDMSbaJilAQg5jjlRtysWHQcoIMuD0_m.png
[2]Responder chain - 將所有會對 touch or motion event 有興趣的 responder [3] 串起來 依序接收 event 直到有 responder 接手處理。
[3]Responder : UIResponder 或後代類別建立的物件簡稱。

The Application Runtime Environment

iPhone 應用程式設計非常強調速度,因為手持裝置的特性,太過漫長的啟動時間和過久的 關閉程序都會讓使用者有不好的體驗。

Fast Launch, Short Use

iPhoen 要求程式越快啟動越好,並且嚴格限制程式關閉必須在5秒以內,如果5秒作不完 關閉程式的流程,作業系統會強制中斷。

基於快速進入離開的特性,設計應用程式的時候應該特別注意狀態的回覆,也就是說當程式 離開時最好能保有目前狀態,下一次進入程式可以馬上銜接上上次離開狀態。

直到 iPhone OS 4.0 為止,切換應用程式一定是將先前的應用程式關閉在執行新的應用 程式,不過 iPhone OS4.0 有針對多工服務做了大幅度的加強,現在應用程式可以在切換 的時候移去背景 (background) 並隨時可以切換回來,作業系統會負責這一連串的資源 管理切換動作。

雖然 iPhone OS 4.0 在一定條件下切換應用程式時可以讓應用程式停留在背景而非 關閉,但是這些停留在背景的應用程式隨時都可能因為系統需要記憶體或其他資源而移除, iPhone OS 4.0 會先移除最久沒有重新喚起的背景應用程式。 因此應用程式還是必須 要做好狀態保存的工作。

Security

The Application Sandbox

基於安全的理由,iPhone 的應用程式資料都被限制在一定的位置,其他應用程式沒有權限 進入存取,在文件中都稱呼這個限制的區域為 sandbox,當應用程式載入以後作業系統會 計算出一個唯一的識別符號,並用這個唯一的識別符號當作應用程式的home目錄。

File Protection

iPhone OS 4.0 新增的設計,應用程式可以將指定的檔案加密,使用者必須明確的輸入 對應的密碼加以解密讓檔案可以使用。

Keychain Data

應用程式可以使用 iPhone Keychain service 來管理帳號密碼,Keychain資料由 iPhone OS 集中保存,並非和 Applicaiton sandbox 擺在一起,當應用程式移除 的時候 iPhone 也會負責將對應的 keychain data 移除。

細節請參考 "Keychain Service Programming Guide"

iPhoen File system

幾個重要的目錄

因為安全需求應用程式只有幾個特定的地方可以寫入資料或是設定,當應用程式 安裝後 iPhone 會位應用程式建立所謂的 sandbox , 每個 sandbox 都 會有一個主目錄( <Application_Home> ) 下面列舉重要的子目錄:

<Application_Home>/AppName.app Bundle Directory
<Application_Home>/Documents/ 存放可以和桌上型電腦share的檔案,iTunes會同步這個子目錄。
<Application_Home>/Library/Preferences 應用程式的設定資料可以用CFPreferences API進行存取,iTunes會同步這個子目錄。
<Application_Home>/Library/Caches 如果有任何資料希望在應用程式結束後可以保存下來供下次使用摳噁以寫入這目錄。
<Application_Home>/tmp/ 暫存資料應用程式關閉後即清空。

和電腦共享檔案

拜iPad所賜iPhone現在也可以和電腦共享檔案了,要注意的是檔案共享只限於iPhone和電腦 應用程式間無法分享。 這些共享的檔案透過iTunes會放進 <Application_Home>/Documents/ 目錄,請依照 下面步驟使用這功能:

  1. 將 UIFileSharingEnable key加入應用程式的Info.plist並設成 YES 。
  2. 將檔案放進 Documents/
  3. 當連上 iTunes 會有一個 File share 出現在應用程式的頁面。

** 雖然 iPhone 4.0 的文件上面有說明到這個 iPad 的 iPhone OS 3.2 才有的功能 ,但是其實安裝 4.0 後並沒有看到相關的設定,這部份還須確認正式上市後是否會加入。

The Application Bundle

寫好應用程式後編譯時 xcode 會將應用程式包裝成一個 bundle [4] ,這編列一般 設定好的檔案和其意義:

App 執行檔,副檔名為.app
Setting.bundle Application preference
Icon.png 57X57 png 圖檔,顯示在springboard [5] 上得應用程式圖示。
Icon-setting.png 29X29 png 圖檔,顯示在設定裡 TableView 顯示用。
MainWindow.nib 預設 main nib file可在info.plist中修改。
Default.png 480X320 png 圖檔,當應用程式讀取時的背景圖案。
iTunesArtwork 512X512 和 Icon.png 一樣,不過是給 App Store用的。
info.plist 應用程式設定
en.lproj 國際化資源檔
fr.lproj...  

存取 Bundle 的程式範例

NSString *imgPath = [[NSBundle mainBundle] pathForResource:@"sun"
                                                    ofType:@"png"];
UIImage *img = [[UIImage alloc] initWithContentsOfFile:imagePath];
[4]Bundle 就是經過設計有意義的目錄。
[5]iPhone 的 Finder,將所有應用程式排列並根據使用者觸碰執行對應應用程式。

The Information Property List

Runtime Configuration Guidelines 有列舉所有支援的 key 和代表的意義,有閒可以參考。

要得到 inof.plist 的資料可以使用 NSBundle 的兩個 method objectForInfoDictionaryKey:infoDictionary , 建議使用 objectForInfoDictionaryKey: 即可。

從 info.plist 取資料的程式範例:

// value = icon file name
NSString *value = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIncoFile"];

Handling Critical Application Termination(event)

章節解釋重要的系統事件 (System Event)。

Observing Low-Memory Warnings

iPhone 記憶體不像 OSX 上面可以使用硬碟空間作為虛擬記憶體的媒介,因此記憶體使用 上必須更加小心。

iPhone SDK 提供種方法可以讓應用程式得到記憶體不足的警告,並做必要的釋放記憶體處 理來避免應用程式被強迫關閉。

  • 在 application delegate 物件中 implement applicationDidReceiveMemoryWarning:
  • Override didReceiveMemoryWarning: in custom UIViewController class
  • UIApplicationDidReceiveMemoryWarningNotification 註冊通知。

特殊系統行為

因為 iPhone 是資源受限的攜帶式系統,許多設計不同於傳統 desktop system。

The Virtual Memory System

iPhone 應用程式受限於實體記憶體大小,寫程式的時候請注意這點。 iPhone 並沒有 desktop system 利用硬碟做虛擬記憶體 page switch 類似的設計。

The Automatic Sleep Timer

為了省電目的 iPhone 在一段時間沒有收到 touch event 會將螢幕的背燈關掉,如果 不希望 auto sleep 這功能啟動,請使用下面程式片段:

UIApplication *app = [UIApplication sharedApplication];
app.idleTimerDisabled = YES;

Warning

關掉 idle timer 會快速消耗電力,請特別注意使用時機。

Executing Code in the Background

iPhone OS 4.0 應用程式終於可以支援多工了,多工牽扯的技術很多這邊只是大概的概念與架構 詳細資訊請自行參閱相關文件。

進入背景(Background)

多工最重要的變動就是應用程式在 iPhone OS 4.0 之前,關閉的時候會收到 - applicationWillTerminate: 處理程式 關閉的清理或是儲存工作,在 iPhone OS 4.0 應用程式改收到 - applicationDidEnterBackground: 讓程式處理必要的資料儲存工作,以便進入 suspend 狀態。

基本上處理的工作是大同小異的,因為就算進入背景 suspended 隨時都會因為 記憶體不足而被系統無預警的刪除,應用程式要有隨時會死亡的決心,做好準備。

判斷機器是否支援多工

UIDevice* device = [UIDevice currentDevice];
BOOL backgroundSupported = NO;
if ([device respondsToSelector:@selector(isMultitaskingSupported)])
   backgroundSupported = device.multitaskingSupported;

宣告支援的背景服務

在 Info.plist 中的 UIBackgroundModes 設定,值的形態為字串陣列,可以包含 下面三種服務 (可以多選)。

  • audio - 應用程式將可以在背景放音樂。
  • voip - 應用程式可以在背景接收 internet phone call,類似 skype。
  • location - 應用程式可以在背景持續追蹤 location,比如導航系統可以繼續在背景服務。

除此之外應用程式還可以要求進入背景後有額外的時間在背景繼續執行程式,以及註冊 local notification。

額外的執行時間

應用程式可以呼叫 beginBackgroundTaskWithExpirationHandler: 來取得額外的執行時間 ,請記住每次使用 beginBackgroundTaskWithExpirationHandler: 都要記得呼叫 endBackgroundTask: 來 釋放資源喔,就像 retain 和 release 一樣。

下面範例示範如何和系統要求額外的執行時間(不然只有5秒),首先所有用到 dispatch main queue (dispatch_get_main_queue()) 都是用來處理同步問題的 Code 基本上照抄就好,對於這些看不懂的 dispatch_XXXX 有興趣的可以參考小弟另外的筆記:

http://psvsps2.blogspot.com/2010/04/gcd.html

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    UIApplication*    app = [UIApplication sharedApplication];

   // bgTask 單純是 instance variable 可以換成你喜歡的。
   // 這行保證不會重複做到之後的 code
   NSAssert(self->bgTask == UIInvalidBackgroundTask, nil);

   self->bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
      // Synchronize the cleanup call on the main thread in case
      // the task actually finishes at around the same time.
      dispatch_async(dispatch_get_main_queue(), ^{
         if (self->bgTask != UIInvalidBackgroundTask)
         {
            [app endBackgroundTask:self->bgTask];
            self->bgTask = UIInvalidBackgroundTask;
         }
      });
   }];

   // Start the long-running task and return immediately.
   dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

      // 你想要的工作放這邊.

      // Synchronize the cleanup call on the main thread in case
      // the expiration handler is fired at the same time.
      dispatch_async(dispatch_get_main_queue(), ^{
         if (self->bgTask != UIInvalidBackgroundTask)
         {
            [app endBackgroundTask:self->bgTask];
            self->bgTask = UIInvalidBackgroundTask;
         }
      });
   });
}

官方提醒的八個要點

  • Do not make any OpneGL ES call from your code,因為背景不支援。
  • Cancel any nerwork-related services before being suspended,因為沒有多工 網路服務。
  • Save your application state before moving to the background,因為在背景的 應用程式隨時都有被系統從記憶體中刪除的可能。
  • Release any unneeded memory when moving to the background,出自於公德心, 程式都移到背景suspend了何必站的茅坑不拉屎。
  • Avoid using share system resource,原因同上一條。
  • Remove sensitive information from views before moving to the background, 因為應用程式要移到背景前,系統會拍個照等應用程式有機會出來時會先在螢幕上面擺上照片如此, 使用者會有"哇!好快喔的錯覺",而類似密碼這種敏感的資訊假如不處理就很可能被照下了,然後 手機被別人拿去玩就不小心看到敏感資訊。
  • Avoid updating your windows and views,因為沒機會顯示。
  • Do minimal work while running in the background,因為 background 資源是大家 共享的,有公德心一點不要再裡面算 DNA 排列。

沒有留言:

Related Posts with Thumbnails