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)是一個字符型指針數組。app
默認的argc爲1,即包含一個參數,這個參數是程序完整路徑;咱們也能夠添加一些額外的參數來測試一下:less
P.s. 經過Xcode菜單欄的Product—Scheme—Edit Scheme打開編輯窗體。ide
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測試
能夠看出參數是以空格來分割的。第一個參數爲真機上程序的完整路徑。fetch
main函數中調用了一個UIApplicationMain的函數,先來看看這個函數的原型:ui
int UIApplicationMain ( int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName );
前面兩個參數前面已經分析過了,重點是最後兩個參數principalClassName和delegateClassName:this
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類型的對象,存儲的是程序啓動的緣由。
NSURL *url = (NSURL *)[launchOptions valueForKey:UIApplicationLaunchOptionsURLKey];
程序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)運行,下面是兩種運行方式的圖示:
加載到前臺:
加載到後臺:
前臺運行比較常見,後臺運行出如今較後的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:方法中執行下列操做:
當程序回到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的鍵值。