应用程序 & VC生命周期

一.  应用程序的活动状态

1. not running :应用关闭状态。程序未打开没有运行的状态。

2. inactive :未激活状态。程序在前台运行,不能处理事件。

注意:当应用从一个状态切换到另一个状态时,中途过渡会短暂停留在此状态。在此状态停留时间比较长的情况是:当用户锁屏时,或者系统提示用户去响应某些(来电、短信等)事件的时候。

3. active :激活状态。应用正在前台运行,并且对事件进行响应和处理。是应用正在前台运行时所处的正常状态。

4. BackGroud :后台状态。应用在后台且能执行代码,会在此状态保留一定时间,时间超时就会进入应用程序的挂起状态,经过特殊请求可以使其长期处于此状态。

5. Suspended :挂起状态。系统会自动把程序变成这个状态而且不会发出通知。

注意:此时应用在后台,和 backGround 的不同在于挂起状态的应用程序不可以执行代码,程序还是停留在内存中的。当系统内存低时,系统就把挂起的程序清除掉,为前台程序提供更多的内存。

函数执行顺序

1. 首先执行的是main.m 类中的main函数。创建一个UIApplication 对象, 创建对应的代理对象 UIAppdelegate 

2. 应用程序启动时,UIApplication 会初始化一些核心对象,开启RunLoop 运行循环,用以处理事件和更新视图界面。

3. UIApplication 负责协调 系统和 其他对象之间的交互.

4. UIAppdelegate 监听Application 中各种状态等,处理各种UIApplication 代理回调方法

5. 应用的启动时间:触发app + main函数执行 + 加载mainUI 文件 + 第一次加载完成以及加载其他UI 完成  + 进入未激活状态所用的时间 = app的启动时间.

进入前台的生命周期


运行状态的代理回调方法

//  1. 未运行状态,告诉代理-程序已经进入启动状态但是还没有进入未激活状态和状态保存在第一次完成加载mainUI时进行加载

1. - (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions

/ / 2.  完全加载UI 成功 时进入此方法通知app完成启动进入未激活状态

2. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

/ / 3.  应用即将进入未激活状态,在此状态中应用程序无法接受事件进行处理,比如来电话了,  从前台运行状态离开时执行。

3.  - (void)applicationWillResignActive:(UIApplication *)application

// 4. 应用接受到事件时通知app进入激活状态,可以接收并处理事件

4. - (void)applicationDidBecomeActive:(UIApplication *)application

// 5. 应用进入后台,需要在后台继续执行的代码在此函数中处理

5. - (void)applicationDidEnterBackground:(UIApplication *)application

// 6.  应用将从后台移入前台, 还没有到Active状态时执行的函数。

6. - (void)applicationWillEnterForeground:(UIApplication *)application

// 7. 应用将要退出,在此保存一些数据和应用状态,以及应用退出前的内存清理工作, 如果当前处在suspended状态,此方法不会被调用,应用直接被系统回收。

7. - (void)applicationWillTerminate:(UIApplication *)application

// 8. 应用完成载入

8. - (void)applicationDidFinishLaunching:(UIApplication*)application

程序运行状态的变化图


前台状态 & 中断响应

当一个基于警告式的中断发生时就要有中断响应的操作。

例如:播放音乐时突然接到电话,锁屏键也是一种程序中断方式,当我们按锁屏键时,系统屏蔽了所有的交互事件并通知app进入未激活状态和放入后台进行待续操作!

基于响应中断的操作

注意⚠️⚠️⚠️:应用通过事件监听循环对各种事件进行监听,监听到打电话操作时首先会把当前程序列入未激活状态。 接着询问是否对当前事件响应以及放弃,如果放弃则变为激活状态继续执行当前操作。

如果响应则进入其他app,需要在进入未激活状态的代理方法中,对当前事件进行状态保存

例如:在applicationWillResignActive: 方法中

1. 停止timer 和其他周期性的任务

2. 挂起任何分发的队列和不重要的操作队列(如:挂起不必要的网络请求操作)。

3. 暂停视频的播放,游戏等

4. 减少OpenGL ES的帧率

当程序回到active状态 ,    applicationDidBecomeActive:    方法时,上面提到的任务重新开始,比如重新开始timer,  继续分发队列,提高OpenGL ES的帧率。游戏和音乐等保持暂停状态,不能自动开始。


后台状态

应用程序进入后台时:会有一个判断是否可以在后台运行(是否可执行代码操作)。

1. 允许后台运行会一直处于后台运行状态并执行某些代码操作

2. 不允许时,进入挂起状态。

后台运行操作

应用程序进入后台时做些什么?

1:  applicationDidEnterBackgound:  程序可能在后台被杀死

1.1 >方法有 5秒的执行时间。如果还有未完成的任务,程序就会被终止而且从内存中清除。如果需要长时间的运行任务,可调用  beginBackgroundTaskWithExpirationHandler    方法请求后台运行时间,启动线程来处理任务。

1.2 >应执行最少量化的工作。所有不必要的任务都应被推迟。当手机内存即将耗尽时,系统会终止那些挂起suspended的应用以回收内存。那些消耗很多内存同时又处于后台background运行的应用会优先被终止。

2. 发送UIApplicationDidEnterBackgroundNotification 通知,让应用中对进入后台状态敏感的对象 知道当前应用正切向background状态,可以及时作出一些处理操作:

2.1 > : 保存用户数据或状态信息:把没写到磁盘的文件或信息,写到磁盘中。

2.2 > : 尽可能释放内存:系统努力的保持更多的应用程序在后台同时运行。当内存不足时,会终止一些挂起的程序回收内存,那些内存最大的程序首先被终止。

2.3 > 在基于网络sockets的应用中,需要处理连接失败的情况。当应用从后台退出恢复执行时,如果遇到sockets使用错误,要重建socket连接。

注意:和外部附件有通信的应用,当切向background状态时,系统会发送一个disconnection 通知。应用必须注册此通知并且使用它去关掉当前的附件访问session。当应用返回foreground时,会有一个与之匹配的通知被发送,给应用提供重新建立session的机会。

2.4 >:  应用界面快照:当applicationDidEnterBackground 方法返回时,系统保存应用界面的快照,并且使用快照图片作为转换动画。如果在应用界面中有涉及到敏感信息的视图,应在该方法返回前隐藏或者修改这些视图。

3 :清除缓存

1. Core Animation的后备存储 ,释放所有的核心动画层的后备存储,以避免这些层继续在屏幕上显示,不改变当前层的属性。不释放层对象自已。

2. 移除所有对缓存图像的引用。(没有强引用,会被释放)

3. 释放一些系统管理的其它的数据缓存。


返回前台的运行操作

应用程序返回前台 foreground时做些什么?

1. 处理通知队列:方向改变、偏好改变、以及其它影响应用外观或状态的通知

2. 更新视图

注: app处于suspended 状态时,不能执行任何代码。因此不能处理在挂起期间发来的通知:比如方向改变,时间改变,设置的改变还有其他影响程序展现的或状态的通知。在程序返回后台或前台时,程序都要正确的处理这些通知。

应用终止

应用正在前台或后台运行将被终止时,系统会调用 applicationWillTerminate: 方法 ,保存用户数据或应用状态信息,该方法最长运行时限为 5秒,此方法不能申请额外的后台执行时间。过期应用即被kill掉并且移除内存。


The Main Run Loop 主运行循环

Main Run Loop 负责处理用户相关的事件(更新视图 UI  界面)。UIApplication对象在程序启动时启动main run Loop, 运行在主线程==保证接收到用户相关操作的事件是按顺序处理的。主线程是串行队列,先进先出。

Main Run Loop  处理事件流程图


1. 用户操作设备,相关的操作事件被系统生成并通过 UIKit的 指定端口分发。事件在内部排成队列,一个个的分发到Main run loop 去做处理。

2. UIApplication对象是第一个接收到时间的对象,它决定事件如何被处理。触摸事件分发到主窗口,窗口找到响应触摸事件的View。类似于触摸事件,远程操控事件(线控耳机等)都是由app 的  responder objects 对象处理的。



参考:

iOS-- 事件传递 & 响应机制

iOS - 为什么要在主线程中操作UI -


日常使用场景:

1. 当URL请求到达,如果应用没在运行,则会被启动并且移到前台运行以打开URL。

1. willFinishLaunchingWithOptions

2. application:didFinishLaunchingWithOptions:

3. application:openURL:sourceApplication:

4. applicationDidBecomeActive

2. 当URL请求到来时,如果应用正在background运行或被suspended,它将会被移到前台以打开URL。如 从第三方分享回来,  或者双击home键,再打开程序

1. applicationWillEnterForeground

2. application:openURL:sourceApplication:

3. applicationDidBecomeActive


二:viewController 的生命周期 

vc生命周期函数调用顺序

1.1:initWithNibName: bundle :xib创建, 从nib 返回一个新初始化的视图控制器。

使用场景1 主动调用:ViewControllerWithXib *vc = [[ViewControllerWithXib alloc]initWithNibName:@"ViewControllerWithXib" bundle:nil];

只在控制器VC中实现此方法。

使用场景2 被动调用:VC 的  init 方法会自动调用这个方法。

ViewControllerWith * vc = [[ViewControllerWith alloc]init];

1.2: initWithCoder :此时子控件没有被创建

使用场景1 在VC 控制器中 从stroryBoard加载vc 控制器

UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"storyboard" bundle:[NSBundle mainBundle]];   //这里的bundle 写nil也可以代表是mainBundle

 MyViewController *vc = [storyboard  instantiateViewControllerWithIdentifier:@"MyViewController"];

使用场景2  view 中:从XIB加载view 

UIView *view = [[[NSBundle mainBundle] loadNibNamed:@"MyView" owner:self options:nil]lastObject];

执行顺序:initWithCoder --- > awakeFromNib。

1.3: awakeFromNib: 该方法在UIView  类中实现。

awakeFromNib方法被调用时,子控件创建完毕, 可在这里进一步对View进行初始化

1.4 : initWithFrame : 纯代码创建UIview 类对象时调用,不涉及xib或storyboard。 只有UIview 中可实现这个方法

使用场景1 被动调用:view 的  init 方法会自动调用这个方法。

MyView *view = [[MyView alloc]init];

使用场景 2 主动调用

UIView *view = [[UIView alloc] initWithFrame:CGRectZero];


参考:view controller 和view的初始化 -

iOS中的各种加载方法(initWithNibName,loadNibNamed,initWithCoder,awakeFromNib等等)简单使用 -


2. loadView:控制器的view 被访问且为空的时候调用

2.1: loadView方法不应该直接被调用,而是由系统调用,它会加载或创建一个view并把它赋值给UIViewController的view属性。

2.2: 视图控制器如果是通过nib创建,创建view时,首先会根据nibName去找对应的nib文件然后加载。如果nibName为空或找不到对应的nib文件,则会手动创建一个空视图

注意:假设在内存警告时释放view属性:self.view = nil。在重写loadView方法的时候,不要调用父类的方法

loadView:每次访问view时,就会调用self.view的get方法,在get方法中判断self.view==nil,不为nil就直接返回view,等于nil就去调用loadView方法。loadView方法会去判断有无指定storyBord/Xib文件,如果有就去加载storyBord/Xib描述的控制器view,如果没有则系统默认创建一个空的view,赋给self.view。loadView方法有可能被多次调用(每当访问self.view并且为nil时就会调用一次);

3. viewDidLoad:view加载完成时调用,也有可能执行多次(self.view==nil且被访问时)

使用场景:对于各种初始化数据的载入,初始设定、修改约束、移除视图等很多操作都可以这个方法中实现。

4. viewWillAppear:系统在载入所有的数据后,将会在屏幕上显示视图时会调用

使用场景:在这个方法对即将显示的视图做进一步的设置。比如,设置设备不同方向时该如何显示;设置状态栏方向、设置视图显示样式等。当APP有多个视图时,上下级视图切换。视图的数据更新等。

5. viewWillLayoutSubviews:view即将布局其Subviews。

使用场景: 比如view 的bounds 改变了(例如:状态栏从不显示到显示,视图方向变化),要调整Subviews的位置,在调整之前要做的工作可在该方法实现

6. viewDidLayoutSubviews:view已经布局其Subviews

使用场景:在这里操作布局调整完成之后需要做的工作。

参考:iOS 中setNeedsDisplay,setNeedsLayout,layoutSubViews,layoutIfNeeded等一些方法的使用 - HJiang - 博客园


7. viewDidAppear:视图已经显示出来了

使用场景:可以对正在显示的视图做进一步的设置。

8. viewWillDisappear:在视图切换时,当前视图即将被移除、或被覆盖时调用

注意:此时还没有调用removeFromSuperview。

9. viewDidDisappear:view已经消失或被覆盖

注意:此时已经调用removeFromSuperView;

10:dealloc  视图被销毁

使用场景:需要在这个方法中对,在init 和viewDidLoad 中创建的对象进行释放。

11. didReceiveMemoryWarning

1. 内存足够时,app的视图通常会一直保存在内存中

2. 内存不够时,一些没有正在显示的viewController就会收到内存不足的警告,然后就会释放自己拥有的视图。但是系统只会释放内存,并不会释放指向对象的指针。要将不需要显示在内存中的对象进行指针释放操作,将其指针置nil。 先调用父类 的 didReceiveMemoryWarning

didReceiveMemoryWarning


内存警告时view的处理机制 (iOS6以前 & 之后)

iOS6 以前:(不包括iOS6)内存警告时,会在 viewDidUnload 中手动的回收ViewController中的子视图或者 view([self.view removeFromSuperView];  self.view = nil;)当view再次被访问时,就会再次调用 loadView 方法,viewController的view以及其子视图都会被重建。

内存警告时:viewDidUnload一定会被调用; loadView会被调用多次

iOS6 以后: 苹果废弃了viewWillUnload 和 viewDidUnload 方法,所以以前在viewDidUnload中处理内存警告的代码就需要移动到didReceiveMemoryWarning 中。

iOS6 及以后:内存警告时系统会回收 ViewController 的View 的 CALayer 里的BitMap(CABackingStore类型,它的内容是直接用于渲染到屏幕,它是View消耗内存的大户)。view 和 calayer占的内存极少, 数量级也就在byte和kbyte之间,所以系统只回收了BitMap。

1. 系统会给用于渲染视图的数据(BitMap)内存打一个volatile 标记,表明这部分内存是可能随时被其它数据占用。

 2. 平时没内存警告时正在使用的内存标记为In use,VC 的View的架子结构并不会回收,当View再次被访问时,虽然View的架子结构会重建,但触发drawRect来渲染界面时,如果view对应的BitMap数据内存没有被占用则会被View的drawRect方法直接渲染出来且内存被标记为in use,从而这块内存又可以独享了;

3. 被释放回收的标记为Not in use。如果已被其它数据占用,那么BitMap必须要重建。整个重建过程不再是由loadView来做的,是通过对view的访问来触发的。

如果在didReceiveMemoryWarning 里把ViewController的View也回收了( [self.view removeFromSuperview];self.view = nil;),那么当再次有对View访问时,loadView会被调用以进行完全最彻底的重建(想想也是,ViewController的View都没了,不调loadView来重建那怎么办呢)。

设计的优点:视图结构和视图数据的分离

内存警告后系统只回收视图数据,只是做个标记,不是完全的清掉。减小了每次重建BitMap 的成本,同时开放这部分内存可以随时让别的数据占用;

面试题:

iOS页面加载生命周期 -   push & pop -- presentViewController & dismissViewController 方法调用顺序


参考:

iOS--viewcontroller生命周期以及内存警告处理原理    

iOS App的生命周期 - CocoaChina_一站式开发者成长社区

iOS应用的生命周期详解_die_word的博客-CSDN博客_ios生命周期  很多图,有注释,较为详细

iOS应用程序生命周期(前后台切换,应用的各种状态)详解_空杯子-CSDN博客  多图

IOS app生命周期 - CodingForever - 博客园 较多讲解不同状态下系统会做的事,和应该在此状态下注意要写的代码

IOS 生命周期 - 知乎 有viewContorller 的生命周期详解

你可能感兴趣的:(应用程序 & VC生命周期)