聊一聊iOS的那些生命周期
HasHLI
8年前
<p>iOS应用程序的生命周期,还有程序是运行在前台还是后台,应用程序各个状态的变换,这些对于开发者来说都是很重要的。iOS系统的资源是有限的,应用程序在前台和在后台的状态是不一样的。在后台时,程序会受到系统的很多限制,这样可以提高电池的使用和用户体验。</p> <p>本文所要说到的生命周期,也不仅仅只是应用生命周期;还包括,视图生命周期。</p> <h2>应用生命周期</h2> <p>iOS的应用程序一共有5种状态:</p> <ul> <li>Not Running(非运行状态)</li> </ul> <p>应用没有运行或被系统终止。</p> <ul> <li>Inactive(前台非活动状态)</li> </ul> <p>应用正在进入前台状态,但是还不能接受事件处理。</p> <ul> <li>Active(前台活动状态)</li> </ul> <p>应用进入前台状态,能接受事件处理。</p> <ul> <li>Background(后台状态)</li> </ul> <p>应用进入后台后,依然能够执行代码。如果有可执行的代码,就会执行代码,如果没有可执行的代码或者将可执行的代码执行完毕,应用会马上进入挂起状态。有的程序经过特殊的请求后可以长期处于Backgroud状态。</p> <ul> <li>Suspended(挂起状态)</li> </ul> <p>处于挂起的应用进入一种“冷冻”状态,不能执行代码。如果系统内存不够,系统就把挂起的程序清除掉,为前台程序提供更多的内存,应用会被终止。</p> <p>作为应用程序的委托对象,AppDelegate类在应用生命周期的不同阶段会回调不同的方法。首先,让我们先了解一下iOS 应用的不同状态及它们彼此间的关系,如下图所示 :</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/fc08bdfe558b2e2c82c874ea5bea7661.png"></p> <p>在应用状态跃迁的过程中,iOS 系统会回调AppDelegate中的一些方法,并且发送一些通知。实际上,在应用的生命周期中用到的方法和通知很多,我们选取了几个主要的方法和通知进行详细介绍,具体如下表所述:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/754f124fac13e7204e014f6904fe40f6.jpg"></p> <p>为了便于观察应用程序的运行状态,为AppDelegate.m中的方法添加一些日志输出,具体代码如下:</p> <pre> <code class="language-objectivec">@implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{ NSLog(@"%@", @"application:didFinishLaunchingWithOptions:"); return YES; } - (void)applicationWillResignActive:(UIApplication *)application{ NSLog(@"%@", @"applicationWillResignActive:"); } - (void)applicationDidEnterBackground:(UIApplication *)application{ NSLog(@"%@", @"applicationDidEnterBackground:"); } - (void)applicationWillEnterForeground:(UIApplication *)application{ NSLog(@"%@", @"applicationWillEnterForeground:"); } - (void)applicationDidBecomeActive:(UIApplication *)application{ NSLog(@"%@", @"applicationDidBecomeActive:"); } - (void)applicationWillTerminate:(UIApplication *)application{ NSLog(@"%@", @"applicationWillTerminate:"); } @end </code></pre> <p>为了让大家更直观地了解各状态与其相应的方法、通知间的关系,下面以几个应用场景为切入点进行系统的分析。</p> <h3>(一)非运行状态——应用启动场景</h3> <p>场景描述:用户点击应用图标的时候,可能是第一次启动这个应用,也可能是应用终止后再次启动。该场景的状态跃迁过程见下图,共经历两个阶段3个状态:Not running →Inactive→Active。</p> <ul> <li>1)在Not running→Inactive 阶段。</li> </ul> <p>调用 application:didFinishLaunchingWithOptions: 方法,发出 UIApplicationDidFinishLaunchingNotification 通知。</p> <ul> <li>2)在Inactive →Active 阶段。</li> </ul> <p>调用 applicationDidBecomeActive: 方法,发出 UIApplicationDidBecomeActiveNotification 通知。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/64d1dd907edb563e89b03cf4afb49f7e.png"></p> <h3>(二)点击Home键——应用退出场景</h3> <p>场景描述:应用处于运行状态(即Active状态)时,点击Home键或者有其他的应用导致当前应用中断。该场景的状态跃迁过程可以分成两种情况:可以在后台运行或者挂起,不可以在后台运行或者挂起。根据产品属性文件(如HelloWorld-Info.plist)中的相关属性Application does not run in background 是与否可以控制这两种状态。如果采用文本编辑器打开HelloWorldInfo.plist文件该设置项对应的键是UIApplicationExitsOnSuspend。</p> <p>状态跃迁的第一种情况:应用可以在后台运行或者挂起,该场景的状态跃迁过程见下图 ,共经历3 个阶段4个状态:Active → Inactive → Background→Suspended。</p> <ul> <li>1)在Active→Inactive 阶段。</li> </ul> <p>调用 applicationWillResignActive: 方法,发出 UIApplicationWillResignActiveNotification 通知。</p> <ul> <li>2)在Inactive →Background阶段。</li> </ul> <p>应用从非活动状态进入到后台(不涉及我们要重点说明的方法和通知)。</p> <ul> <li>3)在Background→Suspended 阶段。</li> </ul> <p>调用 applicationDidEnterBackground: 方法,发出 UIApplicationDidEnterBackgroundNotification 通知。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/093c2354b4f725bb232bb19595bdfb66.png"></p> <p>状态跃迁的第二种情况:应用不可以在后台运行或者挂起,其状态跃迁情况见下图 ,共经历4个阶段5 个状态:Active → Inactive → Background→Suspended→Not running 。</p> <ul> <li>1)在Active →Inactivd 阶段。</li> </ul> <p>应用由活动状态转为非活动状态(不涉及我们要重点说明的方法和通知)。</p> <ul> <li>2)在Inactive →Background阶段。</li> </ul> <p>应用从非活动状态进入到后台(不涉及我们要重点说明的方法和通知)。</p> <ul> <li>3)在Background→Suspended 阶段。</li> </ul> <p>调用 applicationDidEnterBackground: 方法, 发出 UIApplicationDidEnterBackgroundNotification 通知。</p> <ul> <li>4)在Suspended →Not running阶段。</li> </ul> <p>调用 applicationWillTerminate: 方法,发出 UIApplicationWillTerminateNotification 通知。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/8db720d745eb0746215ef1267fa10a50.png"></p> <p>iOS 在iOS 4之前不支持多任务,点击Home键时,应用会退出并中断;而在iOS 4之后(包括iOS 4),操作系统能够支持多任务处理,点击Home键应用会进入后台但不会中断(内存不够的情况除外)。</p> <p>应用在后台也可以进行部分处理工作,处理完成则进入挂起状态。</p> <h3>(三)挂起重新运行场景</h3> <p>场景描述:挂起状态的应用重新运行。该场景的状态跃迁过程如下图所示,共经历3 个阶段4 个状态:Suspended → Background → Inactive → Active 。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/bce656b5ae5d2680a4b7e4f1c766f238.png"></p> <ul> <li>1)Suspended→Background阶段。</li> </ul> <p>应用从挂起状态进入后台(不涉及我们讲述的这几个方法和通知)。</p> <ul> <li>2)Background→Inactive 阶段。</li> </ul> <p>调用 applicationWillEnterForeground: 方法,发出 UIApplicationWillEnterForegroundNotification 通知。</p> <ul> <li>3)Inactive →Active 阶段。</li> </ul> <p>调用 applicationDidBecomeActive: 方法,发出 UIApplicationDidBecomeActiveNotification 通知。</p> <h3>(四)内存清除——应用终止场景</h3> <p>场景描述:应用在后台处理完成时进入挂起状态(这是一种休眠状态),如果这时发出低内存警告,为了满足其他应用对内存的需要,该应用就会被清除内存从而终止运行,该场景的状态跃迁见下图 。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/7f343a5db1e56f0927d41632d81bdf5a.png"></p> <p>内存清除的时候应用终止运行。内存清除有两种情况,可能是系统强制清除内存,也可能是由使用者从任务栏中手动清除(即删掉应用)。内存清除后如果应用再次运行,上一次的运行状态不会被保存,相当于应用第一次运行。</p> <p>在内存清除场景下,应用不会调用任何方法,也不会发出任何通知。</p> <h2>视图生命周期</h2> <p>视图是应用的一个重要组成部分,功能的实现与其息息相关,而视图控制器控制着视图,其重要性在整个应用中不言而喻。</p> <h3>视图生命周期与视图控制器关系</h3> <p>以视图的4 种状态为基础,我们来系统了解一下视图控制器的生命周期。在视图不同的生命周期中,视图控制器会回调不同的方法,具体如下图所示。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/146d3f241ec790ab379f4ee7ad0af83f.png"></p> <p>在视图控制器已被实例化,视图被加载到内存中时调用viewDidLoad方法,这个时候视图并未出现。在该方法中,通常进行的是对所控制的视图进行初始化处理。</p> <p>视图可见前后会调用 viewWillAppear: 方法和 viewDidAppear: 方法;视图不可见前后会调用 viewWillDisappear: 方法和 viewDidDisappear: 方法。4个方法调用父类相应的方法以实现其功能,编码时该方法的位置可根据实际情况做以调整,参见如下代码:</p> <pre> <code class="language-objectivec">-(void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:YES]; //something code } </code></pre> <p>viewDidLoad 方法在应用运行的时候只调用一次,而这上述4 个方法可以被反复调用多次,它们的使用很广泛但同时也具有很强的技巧性。例如,有的应用会使用重力加速计,重力加速计会不断轮询设备以实时获得设备在z 轴、x 轴和y轴方向的重力加速度。不断的轮询必然会耗费大量电能进而影响电池使用寿命,我们通过利用这4个方法适时地打开或者关闭重力加速计来达到节约电能的目的。怎么使用这4 个方法才能做到“适时”是一个值得思考的问题。</p> <p>在低内存情况下,iOS 会调用 didReceiveMemoryWarning: 和 viewDidUnload: 方法。在iOS 6 之后,就不再使用 viewDidUnload: ,而仅支持 didReceiveMemoryWarning: 。</p> <p>didReceiveMemoryWarning: 方法的主要职能是释放内存,包括视图控制器中的一些成员变量和视图的释放。现举例如下:</p> <pre> <code class="language-objectivec">- (void)didReceiveMemoryWarning { self.button = nil; self.myStringD = nil; [myStringC release]; //ARC内存管理情况下不用 [super didReceiveMemoryWarning]; } </code></pre> <p>除了上述5 个方法视图控制器外,还有很多其他方法。</p> <h3>iOS UI 状态保持和恢复</h3> <p>iOS 设计规范中要求,当应用退出的时候(包括被终止运行的时候),需要保持界面中UI元素的状态,当再次进来的时候看到的状态与退出时是一样的。在iOS 之后,苹果提供以下API使得UI状态保持和恢复变得很容易。</p> <p>在iOS 中,我们可以在以下3种地方实现状态保持和恢复:</p> <ul> <li> <p>应用程序委托对象</p> </li> <li> <p>视图控制器</p> </li> <li> <p>自定义视图</p> </li> </ul> <p>恢复标识是iOS为了实现UI状态保持和恢复添加的设置项目。我们还需要在应用程序委托对象AppDelegate代码部分做一些修改,添加的代码如下:</p> <pre> <code class="language-objectivec">-(BOOL) application:(UIApplication *)application shouldSaveApplicationState:(NSCoder *)coder{ return YES; } -(BOOL) application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder{ return YES; } - (void)application:(UIApplication *)application willEncodeRestorableStateWithCoder:(NSCoder *)coder{ [coder encodeFloat:2.0 forKey:@"Version"]; } - (void)application:(UIApplication *)application didDecodeRestorableStateWithCoder:(NSCoder *)coder{ float lastVer = [coder decodeFloatForKey:@"Version"]; NSLog(@"lastVer = %f",lastVer); } </code></pre> <p>其中application:shouldSaveApplicationState:方法在应用退出时调用,负责控制是否允许保存状态,返回YES 情况是可以保存,NO是不保存。</p> <p>application:shouldRestoreApplicationState: 方法在应用启动时调用,负责控制是否恢复上次退出时的状态,返回YES 表示可以恢复,返回NO表示不可以恢复。</p> <p>application:willEncodeRestorableStateWithCoder: 方法在保存时调用,在这个方法中实现UI状态或数据的保存,其中 [coder encodeFloat:2.0 forKey:@"Version"] 语句是保存简单数据。</p> <p>application:didDecodeRestorableStateWithCoder: 方法在恢复时调用,在这个方法中实现UI状态或数据的恢复,其中 [coder decodeFloatForKey:@"Version"] 语句用于恢复上次保存的数据。</p> <p>想要实现具体界面中控件的保持和恢复,还需要在它的视图控制器中添加一些代码。我们在ViewController.m中添加的代码如下:</p> <pre> <code class="language-objectivec">-(void)encodeRestorableStateWithCoder:(NSCoder *)coder{ [super encodeRestorableStateWithCoder:coder]; [coder encodeObject:self.txtField.text forKey:kSaveKey]; } -(void)decodeRestorableStateWithCoder:(NSCoder *)coder{ [super decodeRestorableStateWithCoder:coder]; self.txtField.text = [coder decodeObjectForKey:kSaveKey]; } </code></pre> <p>在iOS 6之后,视图控制器都添加了两个方法—— encodeRestorableStateWithCoder: 和 decodeRestorableStateWithCoder: ,用来实现该控制器中的控件或数据的保存和恢复。</p> <p>其中 encodeRestorableStateWithCoder: 方法在保存时候调用, [coder encodeObject:self. txtField.textforKey:kSaveKey] 语句是按照指定的键保存文本框的内容。</p> <p>decodeRestorableStateWithCoder: 方法在恢复时调用, [coder decodeObjectForKey:kSaveKey] 在恢复文本框内容时调用,保存和恢复事实上就是向一个归档文件中编码和解码的过程。</p> <h2>移除Main.storyboard</h2> <p>每次使用Single View Application模板创建工程之后,总是会有一个Main.storyboard文件,那么,当我们使用代码布局的时候,很显然是不需要它的。那么,如何将它从工程中移除呢?只要进行如下几步即可。</p> <h3>在工程配置中移除关联</h3> <p style="text-align:center"><img src="https://simg.open-open.com/show/985759be12f675de634ac9e63c548a3f.jpg"></p> <p>在TARGETS中,将Main InInterface选项中的值清空并保存设置。</p> <h3>移除Main.storyboard中的关联文件</h3> <p style="text-align:center"><img src="https://simg.open-open.com/show/b1bb3c8bfa938af0628bdead44af113b.png"></p> <p>选择storyboard文件。将类关联文件项清空并保存设置。</p> <h3>移除Main.storyboard文件</h3> <p style="text-align:center"><img src="https://simg.open-open.com/show/fb9d7a27975dc7121ebaa4289fe38adc.jpg"></p> <p>从工程中移除文件。</p> <h3>在AppDelegate中添加代码</h3> <pre> <code class="language-objectivec">- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; ViewController *viewController = [[ViewController alloc] init]; self.window.rootViewController = viewController; self.window.backgroundColor = [UIColor purpleColor]; [self.window makeKeyAndVisible]; return YES; } </code></pre> <p>完成以上几步,运行工程即可,顺利运行,没有出现任何error或waring。</p> <p> </p> <p>来自:http://charsdavy.github.io/2017/04/11/ios-lifecycle/</p> <p> </p>