iOS 应用程序一般都是由自己编写的代码和系统框架(system frameworks)组成,系统框架提供一些基本infrastructure给所有 app 来运行,而你提供自己编写的代码来定制app的外观和行为。因此,了解iOS infrastructure 和它们如何工作对编写app是很有帮助的。
iOS 应用程序的启动执行顺序
启动顺序
首先,来了解一下这张图
以上,就是一个应用程序的执行顺序。接下来,让我们具体的了解一下这个流程
- 程序入口:
执行 main 函数,设置 AppDelegate 称为函数的代理 - 程序完成加载
[AppDelegate application:didFinishLaunchingWithOptions:] - 创建 window 窗口
- 程序被激活
[AppDelegate applicationDidBecomeActive:] - 当点击 home 键时:
程序取消激活状态
[AppDelegate applicationWillResignActive:]
程序进入后台
[AppDelegate applicationDidEnterBackground:] - 点击进入项目工程中:
程序进入前台
[AppDelegate applicationWillEnterForeground:]
程序重新激活
[AppDelegate applicationDidBecomeActive:]
iOS 程序的状态
从上面的这个流程,我们可以发现它包括几个状态:后台、前台、激活、未激活。其实,iOS的应用程序共有5种状态:
- Not running(未运行):程序未启动
- Inactive(未激活):其他两个状态切换时出现的短暂状态。当用户锁屏或者系统提示用户去响应 Alert窗口(如来电、信息等)时
- Active(激活):在屏幕下显示正常的运行状态,该状态下可以接受用户输入并及时更新显示
- Background(后台)::程序在后台且能执行代码。用户按下Home键不久后进入此状态(先进入了Inactive状态,再进入Background状态),然后会迅速进入挂起状态(Suspended)。有的程序经过特殊的请求后可以长期处于Backgroud状态
- Suspended (挂起):程序在后台不能执行代码。普通程序在进入Background状态不久后就会进入此状态。当挂起时,程序还是停留在内存中的,当系统内存低时,系统就把挂起的程序清除掉,为前台程序提供更多的内存
转换过程如图:
程序的入口
首先,下面的函数就是我们经常看到的 main 函数
1 | int main(int argc, char *argv[]) |
main函数的两个参数,iOS中没有用到,包括这两个参数是为了与标准ANSI C保持一致。我们接着看 UIApplicationMain 的参数,前两个和 main 函数的相同,重点是后面的两个,在官方文档中是这样说明的。
1 | / If nil is specified for principalClassName, the value for NSPrincipalClass from the Info.plist is used. If there is no |
后两个参数分别表示程序的主要类(principal class)和代理类(delegate class)。如果主要类(principal class)为nil,将从Info.plist中获取,如果Info.plist中不存在对应的key,则默认为UIApplication;如果代理类(delegate class)将在新建工程时创建。
AppDelegate类
不知道大家有没有认真去研究过我们工程里的 AppDelegate 这个类里的内容呢?其实,它关乎着整个应用程序的生命周期,它包含的6个类方法就是在这几个状态切换过程中调用的。
以下代码就是 AppDelegate.m 中的方法
1 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { |
接下来,就让我们新建一个工程,通过实践看一下它的状态转换过程是否和我们上面所说的一样呢?
当我们启动程序的时候,打印的结果如下:
1 | 2017-11-01 16:47:32.092347+0800 PCConnect[917:142442] -[AppDelegate application:didFinishLaunchingWithOptions:] |
按下home 键时
1 | 2017-11-01 16:49:15.906334+0800 PCConnect[917:142442] -[AppDelegate applicationWillResignActive:] |
重新打开 APP 时:
1 | 2017-11-01 17:51:57.815175+0800 PCConnect[2056:660773] -[AppDelegate applicationWillEnterForeground:] |
我们发现,它和我们上面说的一致。这就是 iOS 程序的状态转化的过程。最后,当我们的程序完全要退出时,将调用 applicationWillTerminate
,来保存用户的一些重要数据以便下次启动时恢复到 APP 原来的状态 。
iOS 视图的生命周期
iOS 应用的视图状态分为以下几种:
当一个视图控制器被创建到在屏幕上显示的时候,代码的执行顺序是:
- alloc 创建对象、分配空间
- init(initWithNibName) 初始化对象,初始化数据
- loadView 从 nib 载入视图,通常这步不需要去干涉。除非你使用纯代码布局。
- viewDidLoad 载入完成,可以进行自定义数据以及动态创建其他控件;
- viewWillAppear 视图将出现在屏幕之前
- viewDidAppear 视图已在屏幕上渲染完成
而当一个视图被移除屏幕并被销毁的时候执行的顺序和上面的差不多相反,具体的如下: - viewWillDisappear 视图将被从屏幕上移除之前执行
- viewDidDisappear 视图已经被从屏幕上移除
- dealloc 视图被销毁,此处需要对你在 init 和 viewDidLoad 中创建的对象进行释放
UIViewController 视图调用的方法
上述对于视图控制器从创建到销毁的过程通常包含如下几种方法,这些方法都是 UIViewController 类的方法:
- (void)viewDidLoad;
- (void)viewDidUnload;
- (void)viewWillAppear:(BOOL)animated;
- (void)viewDidAppear:(BOOL)animated;
- (void)viewWillDisappear:(BOOL)animated;
- (void)viewDidDisappear:(BOOL)animated;
那么,具体每个函数的含义以及如何使用,接下来会给大家具体说明:
- -(void)viewDidLoad;
一个 APP 在载入时会先调用 loadView 方法或者载入 XIB 中创建的初始界面的方法,将视图载入到内存中;然后会调用 viewDidLoad 方法来进一步的设置。
我们会对于各种初始数据的载入,初始设定等很多内容都会在这个方法中实现。这也是,我们最常用的一个方法。 - -(void)viewDidUnload;
内存足够的情况下,软件的视图通常会一直保存在内存中;一旦内存不够,一些没有显示的视图控制器就会收到内存不够的警告,然后就会释放自己拥有的视图,以达到释放内存的目的。但是系统只是释放内存,不会释放对象的所有权,所以通常我们需要在这里将不需要在内存中保留的对象释放所有权,也就是将指针置为 nil.
这个方法通常并不会在视图变换的时候被调用,而只会在系统退出或者收到内存警告的时候才会被调用。但是由于我们需要保证在收到内存警告的时候能够对其作出反应,所以这个方法通常都需要我们去实现。
另外,即使在设备上按了Home键之后,系统也不一定会调用这个方法,因为iOS4之后,系统允许将APP在后台挂起,并将其继续滞留在内存中,因此,viewcontroller并不会调用这个方法来清除内存。 - -(void)viewWillAppear:(BOOL)animated;
系统在载入所有数据后,将会在屏幕上显示视图,这时会先调用这个方法。通常我们会利用这个方法,对即将显示的视图做进一步的设置。例如,我们可以利用这个方法来设置设备不同方向时该如何显示。
另外,当APP有多个视图时,在视图间切换时,并不会再次载入viewDidLoad方法,所以如果在调入视图时,需要对数据做更新,就只能在这个方法内实现了。 - -(void)viewDidAppear:(BOOL)animated;
有时候由于一些特殊的原因,我们不能在viewWillApper方法里对视图进行更新。那么可以重写这个方法,在这里对正在显示的视图进行进一步的设置。 - -(void)viewWillDisappear:(BOOL)animated;
在视图变换时,当前视图在即将被移除、或者被覆盖时,会调用这个方法进行一些善后的处理和设置。
由于在iOS4之后,系统允许将APP在后台挂起,所以在按了Home键之后,系统并不会调用这个方法,因为就这个APP本身而言,APP显示的view,仍是挂起时候的view,所以并不会调用这个方法。 - -(void)viewDidDisappear:(BOOL)animated;
通过重写该方法,我们可以对已经消失或被覆盖或已经隐藏的视图做一些其他操作。
iOS 中 loadView 和 viewDidLoad 的区别
iOS 开发中必不可少的要用到这两个方法,他们都可以用来在视图载入的时候,初始化一些内容。但是它们的区别是什么?
- viewDidLoad 无论你是通过xib文件还是重写loadView方法创建UIViewController的view,在view创建完毕后,最终都会调用viewDidLoad方法
- loadView 每次访问UIViewController的view(比如controller.view、self.view)而且view为nil,loadView方法就会被调用