iOS 應(yīng)用程序生命周期

iOS 應(yīng)用程序一般都是由自己編寫的代碼和系統(tǒng)框架(system frameworks)組成,系統(tǒng)框架提供一些基本infrastructure給所有 app 來運(yùn)行,而你提供自己編寫的代碼來定制app的外觀和行為。因此,了解iOS infrastructure 和它們?nèi)绾喂ぷ鲗?duì)編寫app是很有幫助的。

iOS 應(yīng)用程序的啟動(dòng)執(zhí)行順序

啟動(dòng)順序

首先,來了解一下這張圖


iOS 程序啟動(dòng)圖

以上,就是一個(gè)應(yīng)用程序的執(zhí)行順序。接下來,讓我們具體的了解一下這個(gè)流程

  1. 程序入口:
    執(zhí)行 main 函數(shù),設(shè)置 AppDelegate 稱為函數(shù)的代理
  2. 程序完成加載
    [AppDelegate application:didFinishLaunchingWithOptions:]
  3. 創(chuàng)建 window 窗口
  4. 程序被激活
    [AppDelegate applicationDidBecomeActive:]
  5. 當(dāng)點(diǎn)擊 home 鍵時(shí):
    程序取消激活狀態(tài)
    [AppDelegate applicationWillResignActive:]
    程序進(jìn)入后臺(tái)
    [AppDelegate applicationDidEnterBackground:]
  6. 點(diǎn)擊進(jìn)入項(xiàng)目工程中:
    程序進(jìn)入前臺(tái)
    [AppDelegate applicationWillEnterForeground:]
    程序重新激活
    [AppDelegate applicationDidBecomeActive:]

iOS 程序的狀態(tài)

從上面的這個(gè)流程,我們可以發(fā)現(xiàn)它包括幾個(gè)狀態(tài):后臺(tái)、前臺(tái)、激活、未激活。其實(shí),iOS的應(yīng)用程序共有5種狀態(tài):

  • Not running(未運(yùn)行):程序未啟動(dòng)
  • Inactive(未激活):其他兩個(gè)狀態(tài)切換時(shí)出現(xiàn)的短暫狀態(tài)。當(dāng)用戶鎖屏或者系統(tǒng)提示用戶去響應(yīng) Alert窗口(如來電、信息等)時(shí)
  • Active(激活):在屏幕下顯示正常的運(yùn)行狀態(tài),該狀態(tài)下可以接受用戶輸入并及時(shí)更新顯示
  • Background(后臺(tái))::程序在后臺(tái)且能執(zhí)行代碼。用戶按下Home鍵不久后進(jìn)入此狀態(tài)(先進(jìn)入了Inactive狀態(tài),再進(jìn)入Background狀態(tài)),然后會(huì)迅速進(jìn)入掛起狀態(tài)(Suspended)。有的程序經(jīng)過特殊的請(qǐng)求后可以長期處于Backgroud狀態(tài)
  • Suspended (掛起):程序在后臺(tái)不能執(zhí)行代碼。普通程序在進(jìn)入Background狀態(tài)不久后就會(huì)進(jìn)入此狀態(tài)。當(dāng)掛起時(shí),程序還是停留在內(nèi)存中的,當(dāng)系統(tǒng)內(nèi)存低時(shí),系統(tǒng)就把掛起的程序清除掉,為前臺(tái)程序提供更多的內(nèi)存

轉(zhuǎn)換過程如圖:


狀態(tài)轉(zhuǎn)換

程序的入口

首先,下面的函數(shù)就是我們經(jīng)常看到的 main 函數(shù)

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

main函數(shù)的兩個(gè)參數(shù),iOS中沒有用到,包括這兩個(gè)參數(shù)是為了與標(biāo)準(zhǔn)ANSI C保持一致。我們接著看 UIApplicationMain 的參數(shù),前兩個(gè)和 main 函數(shù)的相同,重點(diǎn)是后面的兩個(gè),在官方文檔中是這樣說明的。

/ If nil is specified for principalClassName, the value for NSPrincipalClass from the Info.plist is used. If there is no  
// NSPrincipalClass key specified, the UIApplication class is used. The delegate class will be instantiated using init.  
UIKIT_EXTERN int UIApplicationMain(int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName);  

后兩個(gè)參數(shù)分別表示程序的主要類(principal class)和代理類(delegate class)。如果主要類(principal class)為nil,將從Info.plist中獲取,如果Info.plist中不存在對(duì)應(yīng)的key,則默認(rèn)為UIApplication;如果代理類(delegate class)將在新建工程時(shí)創(chuàng)建。

AppDelegate類

不知道大家有沒有認(rèn)真去研究過我們工程里的 AppDelegate 這個(gè)類里的內(nèi)容呢?其實(shí),它關(guān)乎著整個(gè)應(yīng)用程序的生命周期,它包含的6個(gè)類方法就是在這幾個(gè)狀態(tài)切換過程中調(diào)用的。
以下代碼就是 AppDelegate.m 中的方法

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // 程序完成載入
    NSLog(@"--- %s ---",__func__); //__func__打印方法名
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    PCCTabBarController *tabBarVC = [[PCCTabBarController alloc] init];
    self.window.rootViewController = tabBarVC;
    [self.window makeKeyAndVisible];
    
    return YES;
}


- (void)applicationWillResignActive:(UIApplication *)application {
    NSLog(@"--- %s ---",__func__);
    /* 當(dāng)應(yīng)用程序從活動(dòng)狀態(tài)(active)變到非活動(dòng)狀態(tài)(inactive時(shí)被觸發(fā)調(diào)用, 這可能發(fā)生在一些臨時(shí)中斷下(例如:來電話、來短信)又或者程序退出時(shí),他會(huì)先過渡到后臺(tái)然后terminate 使用這方法去暫停正在進(jìn)行的任務(wù),禁用計(jì)時(shí)器,節(jié)流OpenGL ES 幀率。在游戲中應(yīng)該在這個(gè)方法里面暫停游戲。 */
    // 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 invalidate graphics rendering callbacks. Games should use this method to pause the game.
}


- (void)applicationDidEnterBackground:(UIApplication *)application {
    NSLog(@"--- %s ---",__func__); //__func__打印方法名
    /* 使用這種方法來釋放共享資源,保存用戶數(shù)據(jù),無效計(jì)時(shí)器,存儲(chǔ)足夠多的應(yīng)用程序狀態(tài)信息來恢復(fù)您的應(yīng)用程序的當(dāng)前狀態(tài),以防它終止丟失數(shù)據(jù)。 如果你的程序支持后臺(tái)運(yùn)行,那么當(dāng)用戶退出時(shí)不會(huì)調(diào)用applicationWillTerminate。 */
    // 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.
}


- (void)applicationWillEnterForeground:(UIApplication *)application {
    /* 先從后臺(tái)切換到非活動(dòng)狀態(tài),然后進(jìn)入活動(dòng)狀態(tài)。 */
    NSLog(@"--- %s ---",__func__);
    // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}


- (void)applicationDidBecomeActive:(UIApplication *)application {
    /* 重啟所有的任務(wù),不管是從非活動(dòng)狀態(tài)還是剛啟動(dòng)程序,還是后臺(tái)狀態(tài)。 */
    NSLog(@"--- %s ---",__func__);
    // 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.
}


- (void)applicationWillTerminate:(UIApplication *)application {
    /* 終止*/
    NSLog(@"--- %s ---",__func__);
    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}

接下來,就讓我們新建一個(gè)工程,通過實(shí)踐看一下它的狀態(tài)轉(zhuǎn)換過程是否和我們上面所說的一樣呢?

當(dāng)我們啟動(dòng)程序的時(shí)候,打印的結(jié)果如下:

2017-11-01 16:47:32.092347+0800 PCConnect[917:142442] --- -[AppDelegate application:didFinishLaunchingWithOptions:] ---
2017-11-01 16:47:32.432588+0800 PCConnect[917:142442] --- -[AppDelegate applicationDidBecomeActive:] ---

按下home 鍵時(shí)

2017-11-01 16:49:15.906334+0800 PCConnect[917:142442] --- -[AppDelegate applicationWillResignActive:] ---
2017-11-01 16:49:16.736169+0800 PCConnect[917:142442] --- -[AppDelegate applicationDidEnterBackground:] ---

重新打開 APP 時(shí):

2017-11-01 17:51:57.815175+0800 PCConnect[2056:660773] --- -[AppDelegate applicationWillEnterForeground:] ---
2017-11-01 17:51:58.102222+0800 PCConnect[2056:660773] --- -[AppDelegate applicationDidBecomeActive:] ---

我們發(fā)現(xiàn),它和我們上面說的一致。這就是 iOS 程序的狀態(tài)轉(zhuǎn)化的過程。最后,當(dāng)我們的程序完全要退出時(shí),將調(diào)用 applicationWillTerminate,來保存用戶的一些重要數(shù)據(jù)以便下次啟動(dòng)時(shí)恢復(fù)到 APP 原來的狀態(tài) 。

iOS 視圖的生命周期

iOS 應(yīng)用的視圖狀態(tài)分為以下幾種:


視圖狀態(tài)

當(dāng)一個(gè)視圖控制器被創(chuàng)建到在屏幕上顯示的時(shí)候,代碼的執(zhí)行順序是:

  1. alloc 創(chuàng)建對(duì)象、分配空間
  2. init(initWithNibName) 初始化對(duì)象,初始化數(shù)據(jù)
  3. loadView 從 nib 載入視圖,通常這步不需要去干涉。除非你使用純代碼布局。
  4. viewDidLoad 載入完成,可以進(jìn)行自定義數(shù)據(jù)以及動(dòng)態(tài)創(chuàng)建其他控件;
  5. viewWillAppear 視圖將出現(xiàn)在屏幕之前
  6. viewDidAppear 視圖已在屏幕上渲染完成
    而當(dāng)一個(gè)視圖被移除屏幕并被銷毀的時(shí)候執(zhí)行的順序和上面的差不多相反,具體的如下:
  7. viewWillDisappear 視圖將被從屏幕上移除之前執(zhí)行
  8. viewDidDisappear 視圖已經(jīng)被從屏幕上移除
  9. dealloc 視圖被銷毀,此處需要對(duì)你在 init 和 viewDidLoad 中創(chuàng)建的對(duì)象進(jìn)行釋放

UIViewController 視圖調(diào)用的方法

上述對(duì)于視圖控制器從創(chuàng)建到銷毀的過程通常包含如下幾種方法,這些方法都是 UIViewController 類的方法:

  • (void)viewDidLoad;
  • (void)viewDidUnload;
  • (void)viewWillAppear:(BOOL)animated;
  • (void)viewDidAppear:(BOOL)animated;
  • (void)viewWillDisappear:(BOOL)animated;
  • (void)viewDidDisappear:(BOOL)animated;
    那么,具體每個(gè)函數(shù)的含義以及如何使用,接下來會(huì)給大家具體說明:
  1. -(void)viewDidLoad;
    一個(gè) APP 在載入時(shí)會(huì)先調(diào)用 loadView 方法或者載入 XIB 中創(chuàng)建的初始界面的方法,將視圖載入到內(nèi)存中;然后會(huì)調(diào)用 viewDidLoad 方法來進(jìn)一步的設(shè)置。
    我們會(huì)對(duì)于各種初始數(shù)據(jù)的載入,初始設(shè)定等很多內(nèi)容都會(huì)在這個(gè)方法中實(shí)現(xiàn)。這也是,我們最常用的一個(gè)方法。
  2. -(void)viewDidUnload;
    內(nèi)存足夠的情況下,軟件的視圖通常會(huì)一直保存在內(nèi)存中;一旦內(nèi)存不夠,一些沒有顯示的視圖控制器就會(huì)收到內(nèi)存不夠的警告,然后就會(huì)釋放自己擁有的視圖,以達(dá)到釋放內(nèi)存的目的。但是系統(tǒng)只是釋放內(nèi)存,不會(huì)釋放對(duì)象的所有權(quán),所以通常我們需要在這里將不需要在內(nèi)存中保留的對(duì)象釋放所有權(quán),也就是將指針置為 nil.
    這個(gè)方法通常并不會(huì)在視圖變換的時(shí)候被調(diào)用,而只會(huì)在系統(tǒng)退出或者收到內(nèi)存警告的時(shí)候才會(huì)被調(diào)用。但是由于我們需要保證在收到內(nèi)存警告的時(shí)候能夠?qū)ζ渥鞒龇磻?yīng),所以這個(gè)方法通常都需要我們?nèi)?shí)現(xiàn)。
    另外,即使在設(shè)備上按了Home鍵之后,系統(tǒng)也不一定會(huì)調(diào)用這個(gè)方法,因?yàn)閕OS4之后,系統(tǒng)允許將APP在后臺(tái)掛起,并將其繼續(xù)滯留在內(nèi)存中,因此,viewcontroller并不會(huì)調(diào)用這個(gè)方法來清除內(nèi)存。
  3. -(void)viewWillAppear:(BOOL)animated;
    系統(tǒng)在載入所有數(shù)據(jù)后,將會(huì)在屏幕上顯示視圖,這時(shí)會(huì)先調(diào)用這個(gè)方法。通常我們會(huì)利用這個(gè)方法,對(duì)即將顯示的視圖做進(jìn)一步的設(shè)置。例如,我們可以利用這個(gè)方法來設(shè)置設(shè)備不同方向時(shí)該如何顯示。
    另外,當(dāng)APP有多個(gè)視圖時(shí),在視圖間切換時(shí),并不會(huì)再次載入viewDidLoad方法,所以如果在調(diào)入視圖時(shí),需要對(duì)數(shù)據(jù)做更新,就只能在這個(gè)方法內(nèi)實(shí)現(xiàn)了。
  4. -(void)viewDidAppear:(BOOL)animated;
    有時(shí)候由于一些特殊的原因,我們不能在viewWillApper方法里對(duì)視圖進(jìn)行更新。那么可以重寫這個(gè)方法,在這里對(duì)正在顯示的視圖進(jìn)行進(jìn)一步的設(shè)置。
  5. -(void)viewWillDisappear:(BOOL)animated;
    在視圖變換時(shí),當(dāng)前視圖在即將被移除、或者被覆蓋時(shí),會(huì)調(diào)用這個(gè)方法進(jìn)行一些善后的處理和設(shè)置。
    由于在iOS4之后,系統(tǒng)允許將APP在后臺(tái)掛起,所以在按了Home鍵之后,系統(tǒng)并不會(huì)調(diào)用這個(gè)方法,因?yàn)榫瓦@個(gè)APP本身而言,APP顯示的view,仍是掛起時(shí)候的view,所以并不會(huì)調(diào)用這個(gè)方法。
  6. -(void)viewDidDisappear:(BOOL)animated;
    通過重寫該方法,我們可以對(duì)已經(jīng)消失或被覆蓋或已經(jīng)隱藏的視圖做一些其他操作。

iOS 中 loadView 和 viewDidLoad 的區(qū)別

iOS 開發(fā)中必不可少的要用到這兩個(gè)方法,他們都可以用來在視圖載入的時(shí)候,初始化一些內(nèi)容。但是它們的區(qū)別是什么?

  • viewDidLoad 無論你是通過xib文件還是重寫loadView方法創(chuàng)建UIViewController的view,在view創(chuàng)建完畢后,最終都會(huì)調(diào)用viewDidLoad方法
  • loadView 每次訪問UIViewController的view(比如controller.view、self.view)而且view為nil,loadView方法就會(huì)被調(diào)用
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容