12 Apr 2017

聊一聊iOS的那些生命周期

iOS应用程序的生命周期,还有程序是运行在前台还是后台,应用程序各个状态的变换,这些对于开发者来说都是很重要的。iOS系统的资源是有限的,应用程序在前台和在后台的状态是不一样的。在后台时,程序会受到系统的很多限制,这样可以提高电池的使用和用户体验。

本文所要说到的生命周期,也不仅仅只是应用生命周期;还包括,视图生命周期。

应用生命周期

iOS的应用程序一共有5种状态:

  • Not Running(非运行状态)

应用没有运行或被系统终止。

  • Inactive(前台非活动状态)

应用正在进入前台状态,但是还不能接受事件处理。

  • Active(前台活动状态)

应用进入前台状态,能接受事件处理。

  • Background(后台状态)

应用进入后台后,依然能够执行代码。如果有可执行的代码,就会执行代码,如果没有可执行的代码或者将可执行的代码执行完毕,应用会马上进入挂起状态。有的程序经过特殊的请求后可以长期处于Backgroud状态。

  • Suspended(挂起状态)

处于挂起的应用进入一种“冷冻”状态,不能执行代码。如果系统内存不够,系统就把挂起的程序清除掉,为前台程序提供更多的内存,应用会被终止。

作为应用程序的委托对象,AppDelegate类在应用生命周期的不同阶段会回调不同的方法。首先,让我们先了解一下iOS 应用的不同状态及它们彼此间的关系,如下图所示 :

在应用状态跃迁的过程中,iOS 系统会回调AppDelegate中的一些方法,并且发送一些通知。实际上,在应用的生命周期中用到的方法和通知很多,我们选取了几个主要的方法和通知进行详细介绍,具体如下表所述:

为了便于观察应用程序的运行状态,为AppDelegate.m中的方法添加一些日志输出,具体代码如下:

@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

为了让大家更直观地了解各状态与其相应的方法、通知间的关系,下面以几个应用场景为切入点进行系统的分析。

(一)非运行状态——应用启动场景

场景描述:用户点击应用图标的时候,可能是第一次启动这个应用,也可能是应用终止后再次启动。该场景的状态跃迁过程见下图,共经历两个阶段3个状态:Not running →Inactive→Active。

  • 1)在Not running→Inactive 阶段。

调用application:didFinishLaunchingWithOptions:方法,发出UIApplicationDidFinishLaunchingNotification 通知。

  • 2)在Inactive →Active 阶段。

调用 applicationDidBecomeActive: 方法,发出UIApplicationDidBecomeActiveNotification 通知。

(二)点击Home键——应用退出场景

场景描述:应用处于运行状态(即Active状态)时,点击Home键或者有其他的应用导致当前应用中断。该场景的状态跃迁过程可以分成两种情况:可以在后台运行或者挂起,不可以在后台运行或者挂起。根据产品属性文件(如HelloWorld-Info.plist)中的相关属性Application does not run in background 是与否可以控制这两种状态。如果采用文本编辑器打开HelloWorldInfo.plist文件该设置项对应的键是UIApplicationExitsOnSuspend。 

状态跃迁的第一种情况:应用可以在后台运行或者挂起,该场景的状态跃迁过程见下图 ,共经历3 个阶段4个状态:Active → Inactive → Background→Suspended。

  • 1)在Active→Inactive 阶段。

调用 applicationWillResignActive:方法,发出UIApplicationWillResignActiveNotification 通知。

  • 2)在Inactive →Background阶段。

应用从非活动状态进入到后台(不涉及我们要重点说明的方法和通知)。

  • 3)在Background→Suspended 阶段。

调用applicationDidEnterBackground:方法,发出UIApplicationDidEnterBackgroundNotification 通知。

状态跃迁的第二种情况:应用不可以在后台运行或者挂起,其状态跃迁情况见下图 ,共经历4个阶段5 个状态:Active → Inactive → Background→Suspended→Not running 。

  • 1)在Active →Inactivd 阶段。

应用由活动状态转为非活动状态(不涉及我们要重点说明的方法和通知)。

  • 2)在Inactive →Background阶段。

应用从非活动状态进入到后台(不涉及我们要重点说明的方法和通知)。

  • 3)在Background→Suspended 阶段。

调用applicationDidEnterBackground:方法, 发出UIApplicationDidEnterBackgroundNotification 通知。

  • 4)在Suspended →Not running阶段。

调用applicationWillTerminate:方法,发出UIApplicationWillTerminateNotification通知。

iOS 在iOS 4之前不支持多任务,点击Home键时,应用会退出并中断;而在iOS 4之后(包括iOS 4),操作系统能够支持多任务处理,点击Home键应用会进入后台但不会中断(内存不够的情况除外)。

应用在后台也可以进行部分处理工作,处理完成则进入挂起状态。

(三)挂起重新运行场景

场景描述:挂起状态的应用重新运行。该场景的状态跃迁过程如下图所示,共经历3 个阶段4 个状态:Suspended → Background → Inactive → Active 。

  • 1)Suspended→Background阶段。

应用从挂起状态进入后台(不涉及我们讲述的这几个方法和通知)。

  • 2)Background→Inactive 阶段。

调用applicationWillEnterForeground: 方法,发出UIApplicationWillEnterForegroundNotification通知。

  • 3)Inactive →Active 阶段。

调用applicationDidBecomeActive:方法,发出UIApplicationDidBecomeActiveNotification 通知。

(四)内存清除——应用终止场景

场景描述:应用在后台处理完成时进入挂起状态(这是一种休眠状态),如果这时发出低内存警告,为了满足其他应用对内存的需要,该应用就会被清除内存从而终止运行,该场景的状态跃迁见下图 。

内存清除的时候应用终止运行。内存清除有两种情况,可能是系统强制清除内存,也可能是由使用者从任务栏中手动清除(即删掉应用)。内存清除后如果应用再次运行,上一次的运行状态不会被保存,相当于应用第一次运行。

在内存清除场景下,应用不会调用任何方法,也不会发出任何通知。

视图生命周期

视图是应用的一个重要组成部分,功能的实现与其息息相关,而视图控制器控制着视图,其重要性在整个应用中不言而喻。

视图生命周期与视图控制器关系

以视图的4 种状态为基础,我们来系统了解一下视图控制器的生命周期。在视图不同的生命周期中,视图控制器会回调不同的方法,具体如下图所示。

在视图控制器已被实例化,视图被加载到内存中时调用viewDidLoad方法,这个时候视图并未出现。在该方法中,通常进行的是对所控制的视图进行初始化处理。

视图可见前后会调用viewWillAppear:方法和viewDidAppear: 方法;视图不可见前后会调用viewWillDisappear: 方法和viewDidDisappear:方法。4个方法调用父类相应的方法以实现其功能,编码时该方法的位置可根据实际情况做以调整,参见如下代码:

-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:YES];
    //something code
}

viewDidLoad 方法在应用运行的时候只调用一次,而这上述4 个方法可以被反复调用多次,它们的使用很广泛但同时也具有很强的技巧性。例如,有的应用会使用重力加速计,重力加速计会不断轮询设备以实时获得设备在z 轴、x 轴和y轴方向的重力加速度。不断的轮询必然会耗费大量电能进而影响电池使用寿命,我们通过利用这4个方法适时地打开或者关闭重力加速计来达到节约电能的目的。怎么使用这4 个方法才能做到“适时”是一个值得思考的问题。

在低内存情况下,iOS 会调用didReceiveMemoryWarning:viewDidUnload:方法。在iOS 6 之后,就不再使用viewDidUnload:,而仅支持didReceiveMemoryWarning:

didReceiveMemoryWarning: 方法的主要职能是释放内存,包括视图控制器中的一些成员变量和视图的释放。现举例如下:

- (void)didReceiveMemoryWarning {
    self.button = nil;
    self.myStringD = nil; 
    [myStringC release];    //ARC内存管理情况下不用
    [super didReceiveMemoryWarning];
}

除了上述5 个方法视图控制器外,还有很多其他方法。

iOS UI 状态保持和恢复

iOS 设计规范中要求,当应用退出的时候(包括被终止运行的时候),需要保持界面中UI元素的状态,当再次进来的时候看到的状态与退出时是一样的。在iOS 之后,苹果提供以下API使得UI状态保持和恢复变得很容易。

在iOS 中,我们可以在以下3种地方实现状态保持和恢复:

  • 应用程序委托对象

  • 视图控制器

  • 自定义视图

恢复标识是iOS为了实现UI状态保持和恢复添加的设置项目。我们还需要在应用程序委托对象AppDelegate代码部分做一些修改,添加的代码如下:

-(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);
}

其中application:shouldSaveApplicationState:方法在应用退出时调用,负责控制是否允许保存状态,返回YES 情况是可以保存,NO是不保存。

application:shouldRestoreApplicationState:方法在应用启动时调用,负责控制是否恢复上次退出时的状态,返回YES 表示可以恢复,返回NO表示不可以恢复。

application:willEncodeRestorableStateWithCoder:方法在保存时调用,在这个方法中实现UI状态或数据的保存,其中[coder encodeFloat:2.0 forKey:@"Version"] 语句是保存简单数据。

application:didDecodeRestorableStateWithCoder:方法在恢复时调用,在这个方法中实现UI状态或数据的恢复,其中[coder decodeFloatForKey:@"Version"] 语句用于恢复上次保存的数据。

想要实现具体界面中控件的保持和恢复,还需要在它的视图控制器中添加一些代码。我们在ViewController.m中添加的代码如下:

-(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];
}

在iOS 6之后,视图控制器都添加了两个方法——encodeRestorableStateWithCoder:decodeRestorableStateWithCoder: ,用来实现该控制器中的控件或数据的保存和恢复。

其中encodeRestorableStateWithCoder:方法在保存时候调用,[coder encodeObject:self. txtField.textforKey:kSaveKey]语句是按照指定的键保存文本框的内容。

decodeRestorableStateWithCoder:方法在恢复时调用,[coder decodeObjectForKey:kSaveKey]在恢复文本框内容时调用,保存和恢复事实上就是向一个归档文件中编码和解码的过程。

移除Main.storyboard

每次使用Single View Application模板创建工程之后,总是会有一个Main.storyboard文件,那么,当我们使用代码布局的时候,很显然是不需要它的。那么,如何将它从工程中移除呢?只要进行如下几步即可。

在工程配置中移除关联

在TARGETS中,将Main InInterface选项中的值清空并保存设置。

移除Main.storyboard中的关联文件

选择storyboard文件。将类关联文件项清空并保存设置。

移除Main.storyboard文件

从工程中移除文件。

在AppDelegate中添加代码

- (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;
}

完成以上几步,运行工程即可,顺利运行,没有出现任何error或waring。