iOS 應(yīng)用程序一般都是由自己編寫的代碼和系統(tǒng)框架(system frameworks)組成托启,系統(tǒng)框架提供一些基本infrastructure給所有 app 來運行葫慎,而你提供自己編寫的代碼來定制app的外觀和行為。因此腥寇,了解iOS infrastructure 和它們?nèi)绾喂ぷ鲗帉慳pp是很有幫助的挡毅。
iOS 應(yīng)用程序的啟動執(zhí)行順序
啟動順序
首先,來了解一下這張圖
以上零蓉,就是一個應(yīng)用程序的執(zhí)行順序。接下來穷缤,讓我們具體的了解一下這個流程
- 程序入口:
執(zhí)行 main 函數(shù),設(shè)置 AppDelegate 稱為函數(shù)的代理 - 程序完成加載
[AppDelegate application:didFinishLaunchingWithOptions:] - 創(chuàng)建 window 窗口
- 程序被激活
[AppDelegate applicationDidBecomeActive:] - 當(dāng)點擊 home 鍵時:
程序取消激活狀態(tài)
[AppDelegate applicationWillResignActive:]
程序進(jìn)入后臺
[AppDelegate applicationDidEnterBackground:] - 點擊進(jìn)入項目工程中:
程序進(jìn)入前臺
[AppDelegate applicationWillEnterForeground:]
程序重新激活
[AppDelegate applicationDidBecomeActive:]
iOS 程序的狀態(tài)
從上面的這個流程箩兽,我們可以發(fā)現(xiàn)它包括幾個狀態(tài):后臺津肛、前臺、激活汗贫、未激活身坐。其實,iOS的應(yīng)用程序共有5種狀態(tài):
- Not running(未運行):程序未啟動
- Inactive(未激活):其他兩個狀態(tài)切換時出現(xiàn)的短暫狀態(tài)落包。當(dāng)用戶鎖屏或者系統(tǒng)提示用戶去響應(yīng) Alert窗口(如來電部蛇、信息等)時
- Active(激活):在屏幕下顯示正常的運行狀態(tài),該狀態(tài)下可以接受用戶輸入并及時更新顯示
- Background(后臺)::程序在后臺且能執(zhí)行代碼咐蝇。用戶按下Home鍵不久后進(jìn)入此狀態(tài)(先進(jìn)入了Inactive狀態(tài)涯鲁,再進(jìn)入Background狀態(tài)),然后會迅速進(jìn)入掛起狀態(tài)(Suspended)有序。有的程序經(jīng)過特殊的請求后可以長期處于Backgroud狀態(tài)
- Suspended (掛起):程序在后臺不能執(zhí)行代碼抹腿。普通程序在進(jìn)入Background狀態(tài)不久后就會進(jìn)入此狀態(tài)。當(dāng)掛起時旭寿,程序還是停留在內(nèi)存中的警绩,當(dāng)系統(tǒng)內(nèi)存低時,系統(tǒng)就把掛起的程序清除掉盅称,為前臺程序提供更多的內(nè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ù)的兩個參數(shù)缩膝,iOS中沒有用到混狠,包括這兩個參數(shù)是為了與標(biāo)準(zhǔn)ANSI C保持一致。我們接著看 UIApplicationMain 的參數(shù)逞盆,前兩個和 main 函數(shù)的相同檀蹋,重點是后面的兩個,在官方文檔中是這樣說明的。
/ 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);
后兩個參數(shù)分別表示程序的主要類(principal class)和代理類(delegate class)俯逾。如果主要類(principal class)為nil贸桶,將從Info.plist中獲取,如果Info.plist中不存在對應(yīng)的key桌肴,則默認(rèn)為UIApplication皇筛;如果代理類(delegate class)將在新建工程時創(chuàng)建。
AppDelegate類
不知道大家有沒有認(rèn)真去研究過我們工程里的 AppDelegate 這個類里的內(nèi)容呢坠七?其實水醋,它關(guān)乎著整個應(yīng)用程序的生命周期,它包含的6個類方法就是在這幾個狀態(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)用程序從活動狀態(tài)(active)變到非活動狀態(tài)(inactive時被觸發(fā)調(diào)用拄踪, 這可能發(fā)生在一些臨時中斷下(例如:來電話、來短信)又或者程序退出時拳魁,他會先過渡到后臺然后terminate 使用這方法去暫停正在進(jìn)行的任務(wù)惶桐,禁用計時器,節(jié)流OpenGL ES 幀率潘懊。在游戲中應(yīng)該在這個方法里面暫停游戲姚糊。 */
// 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ù),無效計時器,存儲足夠多的應(yīng)用程序狀態(tài)信息來恢復(fù)您的應(yīng)用程序的當(dāng)前狀態(tài),以防它終止丟失數(shù)據(jù)。 如果你的程序支持后臺運行授舟,那么當(dāng)用戶退出時不會調(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),然后進(jìn)入活動狀態(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ù)肠槽,不管是從非活動狀態(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:.
}
接下來署浩,就讓我們新建一個工程,通過實踐看一下它的狀態(tài)轉(zhuǎn)換過程是否和我們上面所說的一樣呢扫尺?
當(dāng)我們啟動程序的時候筋栋,打印的結(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 鍵時
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 時:
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)我們的程序完全要退出時姑曙,將調(diào)用 applicationWillTerminate
襟交,來保存用戶的一些重要數(shù)據(jù)以便下次啟動時恢復(fù)到 APP 原來的狀態(tài) 。
iOS 視圖的生命周期
iOS 應(yīng)用的視圖狀態(tài)分為以下幾種:
當(dāng)一個視圖控制器被創(chuàng)建到在屏幕上顯示的時候伤靠,代碼的執(zhí)行順序是:
- alloc 創(chuàng)建對象捣域、分配空間
- init(initWithNibName) 初始化對象,初始化數(shù)據(jù)
- loadView 從 nib 載入視圖,通常這步不需要去干涉焕梅。除非你使用純代碼布局迹鹅。
- viewDidLoad 載入完成,可以進(jìn)行自定義數(shù)據(jù)以及動態(tài)創(chuàng)建其他控件贞言;
- viewWillAppear 視圖將出現(xiàn)在屏幕之前
- viewDidAppear 視圖已在屏幕上渲染完成
而當(dāng)一個視圖被移除屏幕并被銷毀的時候執(zhí)行的順序和上面的差不多相反斜棚,具體的如下: - viewWillDisappear 視圖將被從屏幕上移除之前執(zhí)行
- viewDidDisappear 視圖已經(jīng)被從屏幕上移除
- dealloc 視圖被銷毀,此處需要對你在 init 和 viewDidLoad 中創(chuàng)建的對象進(jìn)行釋放
UIViewController 視圖調(diào)用的方法
上述對于視圖控制器從創(chuàng)建到銷毀的過程通常包含如下幾種方法该窗,這些方法都是 UIViewController 類的方法:
- (void)viewDidLoad弟蚀;
- (void)viewDidUnload;
- (void)viewWillAppear:(BOOL)animated酗失;
- (void)viewDidAppear:(BOOL)animated义钉;
- (void)viewWillDisappear:(BOOL)animated;
- (void)viewDidDisappear:(BOOL)animated级零;
那么断医,具體每個函數(shù)的含義以及如何使用,接下來會給大家具體說明:
- -(void)viewDidLoad;
一個 APP 在載入時會先調(diào)用 loadView 方法或者載入 XIB 中創(chuàng)建的初始界面的方法奏纪,將視圖載入到內(nèi)存中;然后會調(diào)用 viewDidLoad 方法來進(jìn)一步的設(shè)置斩启。
我們會對于各種初始數(shù)據(jù)的載入序调,初始設(shè)定等很多內(nèi)容都會在這個方法中實現(xiàn)。這也是兔簇,我們最常用的一個方法发绢。 - -(void)viewDidUnload;
內(nèi)存足夠的情況下,軟件的視圖通常會一直保存在內(nèi)存中垄琐;一旦內(nèi)存不夠边酒,一些沒有顯示的視圖控制器就會收到內(nèi)存不夠的警告,然后就會釋放自己擁有的視圖狸窘,以達(dá)到釋放內(nèi)存的目的墩朦。但是系統(tǒng)只是釋放內(nèi)存,不會釋放對象的所有權(quán)翻擒,所以通常我們需要在這里將不需要在內(nèi)存中保留的對象釋放所有權(quán)氓涣,也就是將指針置為 nil.
這個方法通常并不會在視圖變換的時候被調(diào)用,而只會在系統(tǒng)退出或者收到內(nèi)存警告的時候才會被調(diào)用陋气。但是由于我們需要保證在收到內(nèi)存警告的時候能夠?qū)ζ渥鞒龇磻?yīng)劳吠,所以這個方法通常都需要我們?nèi)崿F(xiàn)。
另外巩趁,即使在設(shè)備上按了Home鍵之后痒玩,系統(tǒng)也不一定會調(diào)用這個方法,因為iOS4之后,系統(tǒng)允許將APP在后臺掛起蠢古,并將其繼續(xù)滯留在內(nèi)存中奴曙,因此,viewcontroller并不會調(diào)用這個方法來清除內(nèi)存便瑟。 - -(void)viewWillAppear:(BOOL)animated;
系統(tǒng)在載入所有數(shù)據(jù)后缆毁,將會在屏幕上顯示視圖,這時會先調(diào)用這個方法到涂。通常我們會利用這個方法脊框,對即將顯示的視圖做進(jìn)一步的設(shè)置。例如践啄,我們可以利用這個方法來設(shè)置設(shè)備不同方向時該如何顯示浇雹。
另外,當(dāng)APP有多個視圖時屿讽,在視圖間切換時昭灵,并不會再次載入viewDidLoad方法,所以如果在調(diào)入視圖時伐谈,需要對數(shù)據(jù)做更新烂完,就只能在這個方法內(nèi)實現(xiàn)了。 - -(void)viewDidAppear:(BOOL)animated诵棵;
有時候由于一些特殊的原因抠蚣,我們不能在viewWillApper方法里對視圖進(jìn)行更新。那么可以重寫這個方法履澳,在這里對正在顯示的視圖進(jìn)行進(jìn)一步的設(shè)置嘶窄。 - -(void)viewWillDisappear:(BOOL)animated;
在視圖變換時距贷,當(dāng)前視圖在即將被移除柄冲、或者被覆蓋時,會調(diào)用這個方法進(jìn)行一些善后的處理和設(shè)置忠蝗。
由于在iOS4之后现横,系統(tǒng)允許將APP在后臺掛起,所以在按了Home鍵之后什湘,系統(tǒng)并不會調(diào)用這個方法长赞,因為就這個APP本身而言,APP顯示的view闽撤,仍是掛起時候的view得哆,所以并不會調(diào)用這個方法。 - -(void)viewDidDisappear:(BOOL)animated哟旗;
通過重寫該方法贩据,我們可以對已經(jīng)消失或被覆蓋或已經(jīng)隱藏的視圖做一些其他操作栋操。
iOS 中 loadView 和 viewDidLoad 的區(qū)別
iOS 開發(fā)中必不可少的要用到這兩個方法,他們都可以用來在視圖載入的時候饱亮,初始化一些內(nèi)容矾芙。但是它們的區(qū)別是什么?
- viewDidLoad 無論你是通過xib文件還是重寫loadView方法創(chuàng)建UIViewController的view近上,在view創(chuàng)建完畢后剔宪,最終都會調(diào)用viewDidLoad方法
- loadView 每次訪問UIViewController的view(比如controller.view、self.view)而且view為nil壹无,loadView方法就會被調(diào)用