iOS應用程序執行的生命週期

main函數探究

在iOS項目中有一個main.m的文件,它是程序的入口類,代碼如下:

#import <UIKit/UIKit.h>

#import "AppDelegate.h"

int main(int argc, char * argv[])
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

參數argc與argv與標準的C語言main函數一致,argc(arguments count)代表參數個數,argv(arguments value)是一個字符型指針數組。

默認的argc爲1,即包含一個參數,這個參數是程序完整路徑;我們也可以添加一些額外的參數來測試一下:

P.s. 通過Xcode菜單欄的Product—Scheme—Edit Scheme打開編輯窗體。

int main(int argc, char * argv[])
{
    for(int i=0; i<argc; i++)
    {
        NSLog(@"arg %i: %s",i,argv[i]);
    }
    
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

輸出結果:

arg 0: /var/mobile/Applications/C12B5C94-CAD7-489D-AB8E-13280E7CD886/Sample0616.app/Sample0616
arg 1: god
arg 2: bless
arg 3: me
arg 4: New
arg 5: York

可以看出參數是以空格來分割的。第一個參數爲真機上程序的完整路徑。

main函數中調用了一個UIApplicationMain的函數,先來看看這個函數的原型:

int UIApplicationMain (
   int argc,
   char *argv[],
   NSString *principalClassName,
   NSString *delegateClassName
);

前面兩個參數前面已經分析過了,重點是最後兩個參數principalClassName和delegateClassName

principalClassName是應用程序類的名字,該類必須繼承自UIApplication類;如果傳遞nil,UIKit就缺省使用UIApplication類;每一個iOS應用程序都包含一個UIApplication對象,iOS系統通過該UIApplication對象監控應用程序生命週期全過程。

delegateClassName是應用程序委託類的名字,默認爲AppDelegate類,該委託類處理應用程序的生命週期事件和系統事件。

通過調用main函數創建了一個UIApplication對象,UIApplication對象負責監聽應用程序的生命週期時間,並將生命週期事件交由AppDelegate代理對象處理。

iOS程序運行的生命週期

app的狀態有五種:

not running — 沒有啓動app
inactive — app運行在前臺,但是沒有處理任何事件
active — app運行在前臺,並且在處理事件
background — app運行在後臺,還在內存中,並且執行代碼
suspend — app還在內存中,但是不運行任何代碼,如果內存不足,會自動kill掉

app各種狀態之間的改變圖示:

啓動階段

當一個app開始運行前存在一個啓動階段(Launch Time),這個階段包含兩個系統方法:

- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    return YES;
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    return YES;
}

這兩個方法的作用幾乎完全一樣,只是執行順序有先後。AppDelegate類默認出現的是application: didFinishLaunchingWithOptions:,一般情況下只需要處理這個方法就可以了。

didFinishLaunchingWithOptions函數的參數launchOptions是一個NSDictionary類型的對象,存儲的是程序啓動的原因。

  • 用戶(點擊icon)直接啓動程序,launchOptions內無數據;
  • 其它程序通過openURL:方式啓動,則可以通過鍵UIApplicationLaunchOptionsURLKey來獲取傳遞過來的url
    NSURL *url = (NSURL *)[launchOptions valueForKey:UIApplicationLaunchOptionsURLKey];

     

  • 由本地通知啓動,則可以通過鍵UIApplicationLaunchOptionsLocalNotificationKey來獲取本地通知對象(UILocalNotification)
  • 由遠程通知啓動,則可以通過鍵UIApplicationLaunchOptionsRemoteNotificationKey來獲取遠程通知信息(NSDictionary)

程序launchOptions中的可能鍵值可以參考UIApplication Class Reference的”Launch Options Keys”。

didFinishLaunchingWithOptions函數的返回值是一個BOOL類型,將決定是否處理URL資源,如果返回YES,則會由application:openURL:sourceApplication:annotation:方法處理URL。如果應用程序有一個遠程通知啓動,返回值會被忽略

P.s. 如果同時出現了application: willFinishLaunchingWithOptions:和application: didFinishLaunchingWithOptions:,那麼這兩個方法都要返回YES,纔會處理URL資源。

啓動階段結束後進入運行階段,程序可能會進入前臺(foreground)運行,也可能進入後臺(background)運行,下面是兩種運行方式的圖示:

加載到前臺:

foreground      

 

加載到後臺:

background

 

前臺運行比較常見,後臺運行出現在較後的iOS版本,這裏不做很深的研究,只提供一個後臺運行的配置及測試方法:

step 1:在application: didFinishLaunchingWithOptions:裏設置數據獲取時間間隔,並監控當前的程序運行狀態

- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
    
    NSLog(@"current state:%d.(%d,%d,%d)",application.applicationState,
                                         UIApplicationStateActive,
                                         UIApplicationStateInactive,
                                         UIApplicationStateBackground);
    return YES;
}

step 2:在Capabilities標籤頁中啓用”Background Modes”,並勾選”Background fetch”

step 3:在AppDelegate裏實現-application:performFetchWithCompletionHandler:,系統將會在執行fetch的時候調用這個方法。

- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
    //do something...
}

step 4:使用Scheme更改Xcode運行程序的方式。Product—Scheme—Manage Schemes,然後新建或編輯一個scheme項:

step 5:啓動調試。

運行階段(只考慮直接Launch到前臺的情況)

啓動階段執行完後,程序的狀態爲”inactive”,進入到運行階段執行下面方法會變成”active”:

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}

官方留下的註釋翻譯成中文是:“當應用程序處在不活動狀態時,重新啓動一個被暫停(或還未啓動)的任務。如果程序之前就在後臺,根據情況可以刷新用戶界面。”這個方法觸發很頻繁,在程序第一次啓動或程序恢復前臺狀態時,都會先執行這個方法。

中斷情況

接下來考慮中斷情況,有以下幾種常見情況:

1. 按下home鍵或雙擊home鍵點擊任務欄icon運行其它app
2. 有來電通知
3. 有短信或其它app的頂部通知或推送消息。

下面詳細分析下每個動作,打印出觸發的方法及狀態:

case 1:當按下home鍵
function:[applicationWillResignActive], state:[Active]
function:[applicationDidEnterBackground], state:[Background]

case 2:當雙擊home鍵,然後選擇返回當前app:
function:[applicationWillResignActive], state:[Active]
function:[applicationDidBecomeActive], state:[Active]

case 3:當雙擊home鍵,然後選擇其它app:
function:[applicationWillResignActive], state:[Active]
function:[applicationDidEnterBackground], state:[Background]

case 4:當有來電,拒絕接聽:
function:[applicationWillResignActive], state:[Active]
function:[applicationDidBecomeActive], state:[Active]

case 5:當有來電,接聽後並掛斷:
function:[applicationWillResignActive], state:[Active]
function:[applicationDidEnterBackground], state:[Background]
function:[applicationWillEnterForeground], state:[Background]
function:[applicationDidBecomeActive], state:[Active]

case 6:當有短信或頂部的推送消息,點擊查看:
function:[applicationWillResignActive], state:[Active]
function:[applicationDidEnterBackground], state:[Background]

從上面的各種情況可以看出來,只要存在中斷操作,第一個執行的方法都是:

- (void)applicationWillResignActive:(UIApplication *)application
{
    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}

通常情況下,我們應該在applicationWillResignActive:方法中執行下列操作:

  • 停止timer和其它週期性任務
  • 停止任何正在運行的請求
  • 暫停視頻的播放
  • 如果是遊戲那就暫停它
  • 減少OpenGL ES的頻率
  • 掛起任何分發的隊列和不重要的操作隊列(你可以繼續處理網絡請求或其它時間敏感的後臺任務)

當程序回到active狀態,應該使用applicationDidBecomeActive:方法將上面暫停的操作重新開發或恢復運行,比如重新開始timer,繼續分發隊列,提高OpenGL ES的頻率。對於遊戲可能會回到一個暫停狀態,有用戶點擊再開始。

如果程序中斷後進入了後臺,會調用方法:

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}

官方留下的註釋翻譯成中文是:“使用這個方法去釋放共享資源,存儲用戶數據,取消定時器和存儲足夠的應用程序狀態信息以便在終止前恢復到它當前的狀態。如果你的應用程序支持後臺操作,在用戶離開程序後它將代替方法applicationWillTerminate:被調用”。

中斷後返回的情況

中斷情況發生後,再返回當前app,會調用方法:

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}

這裏有個疑問,當app從後臺轉回前臺時,applicationWillEnterForeground:和applicationDidBecomeActive:都會被調用,兩者有什麼區別呢?我的理解是,applicationWillEnterForeground:只有當程序從後臺返回到前臺這一種情況下才會被調用;而applicationDidBecomeActive:除了從後臺返回前臺時被調用,還會在程序運行在前臺時也被調用(例如之前提到的收到來電提醒後取消接聽,雙擊home鍵後依舊返回當前app等操作)。所以applicationWillEnterForeground:適合處理那種加載前只需要執行一次的初始化。

終止階段

當程序終止時將調用方法:

- (void)applicationWillTerminate:(UIApplication *)application
{
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}

這個方法通常是用來保存數據和一些退出前的清理工作,需要要設置UIApplicationExitsOnSuspend的鍵值。

發佈了36 篇原創文章 · 獲贊 0 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章