App 生命周期
应用程序的五种状态

应用程序的五种状态:

  1. 未运行状态:程序尚未启动,或者应用正在运行但是中途被系统停止,当设备内存紧张的时候,也会将挂起的应用当前状态写入到内存,然后退出应用并释放内存,这时候我们虽然能够在任务栏看到图标,但是它已经退出,我们称之为应用墓碑。

  2. 未激活状态:当前应用正在前台运行,但是焦点被其他抢去。比较典型的是用户锁屏或者离开应用去响应来电,信息等事件等时候。还有一种是比较常见的就是在不同状态切换的时候会短暂处于该状态,这时候App会停止运行,但是依然占用内存空间,用于保存当前状态。

  3. 激活状态:当前应用正常运行,应用焦点在当前应用上,所有的事件都会被分发到当前应用,这时应用占用内存和CPU时间。

  4. 后台状态:当前应用还是存活的,并且能够执行代码,但是默认处于这种情况的时间不长(最多十分钟),当我们按下Home键的时候会进入后台状态,如果不继续申请在后台运行的时间会快速进入挂起状态。

  5. 挂起状态: 应用处在后台,并且已经停止执行代码。这时候应用还驻留在内存中,并没有被系统完全回收,只有在系统发出低内存告警的时候,系统才会把处于挂起状态的应用清除出内存给前台正在运行的应用。这时候不占用CPU资源,但是内存依然占用。

AppDelegate

App 生命周期的切换会通过AppDelegate来通知开发者,因此我们可以在AppDelegate中的关键方法中注入我们需要的逻辑从而完成任务,下面是一些常见方法:

//告诉代理进程启动但还没进入状态保存
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions
// 启动基本完成,程序准备开始运行
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
// 当应用程序将要进入非活动状态执行,在此期间,应用程序不接收消息或事件,比如打来电话
- (void)applicationWillResignActive:(UIApplication *)application
// 当应用程序进入活动状态执行,此方法跟上面那个方法相反
- (void)applicationDidBecomeActive:(UIApplication *)application
// 当程序被推送到后台的时候调用。所以要设置后台继续运行,则在这个函数里面设置即可
- (void)applicationDidEnterBackground:(UIApplication *)application
// 当程序从后台将要重新回到前台时候调用,此方法跟上面的那个方法相反
- (void)applicationWillEnterForeground:(UIApplication *)application
// 当程序将要退出是被调用,通常是用来保存数据和一些退出前的清理工作
- (void)applicationWillTerminate:(UIApplication *)application

一些关键场景下的状态及回调
  • 应用冷启动:


相关方法:

-[AppDelegate application:didFinishLaunchingWithOptions:]
-[AppDelegate applicationDidBecomeActive:]
  • 应用热启动 - 点击图标再次打开程序:


-[AppDelegate applicationWillEnterForeground:]
-[AppDelegate applicationDidBecomeActive:]
  • 解锁:
-[AppDelegate applicationWillEnterForeground:]
-[AppDelegate applicationDidBecomeActive:]
  • 按下home键,应用进入后台:


-[AppDelegate applicationWillResignActive:]
-[AppDelegate applicationDidEnterBackground:]
  • 锁屏:

-[AppDelegate applicationWillResignActive:]
-[AppDelegate applicationDidEnterBackground:]
  • 其他应用中断了当前应用:


-[AppDelegate applicationWillResignActive:]

返回

-[AppDelegate applicationDidBecomeActive:]
如何优化性能

主要是如下几个地方:

  1. applicationWillResignActive: 在应用失去焦点的时候:
    可以停止视频播放,游戏播放,减少帧率,挂起不必要的操作队列。
  2. applicationDidEnterBackground:在应用进入后台的时候:
    这时候需要保存用户数据或状态信息,在进入后台时,写到磁盘去,因为程序可能在后台被杀死。再有就是释放尽可能释放的内存。
    可以通过去掉对大图片,大视频,大文件的强引用,在回收的时候就可以先回收这部分资源。
  3. applicationWillTerminate:在系统内存中,必须清除无用数据,但是这个方法有时间限制,默认是5s。
    如果超过时间还有未完成的任务,我们的程序就会被终止而且从内存中清除
App后台限制

iOS设备为了节省电量一般会使用假后台机制

我们开发的应用在用户按下Home之后,App会转入后台运行,系统默认会给应用短暂的后台时间(iOS7 180s,iOS6 600s),来让用户做一些必要的处理,如果这段时间过去之后,还是可以告诉系统未完成任务,还需要点时间,通过向系统发出申请来争取更多的时间,系统批准申请后可以继续运行,但是不会超过10分钟。这10分钟过去之后,无论怎么向系统申请,系统都会挂起应用,直到用户再次点击后才会继续运行。但是这对于一些应用是完全不够的,比如一款即时通讯软件,一般在后台都需要保持长链接不断,才能保证应用在后台还能收到消息,再比如你的音乐播放器软件,即使处于后台你也希望它能够继续播放音乐。
当然iOS也并非全部任务都不能在后台执行,某些任务比如播放音频,获取位置更新,或者从服务器获取最新内容这些任务是可以在后台长时间运行的,只要我们在应用的配置中,指定应用包含这些服务,一旦审核通过,就可以让我们在后台长时间执行任务,而不会被挂起,具体有哪些模式可以在Capabilities 下的Background Mode中选定指定的类型。

下面有个较好的关于Background Mode的翻译文章可以看下
中文翻译:iOS后台模式开发指南

英文原著:Background Modes Tutorial: Getting Started

但是我们最常见的是通过backgroundTaskIdentifier来申请后台运行的时间,下面是关键代码:

- (void)applicationDidEnterBackground:(UIApplication *)application {
self.backgroundTaskIdentifier = [application beginBackgroundTaskWithExpirationHandler:^{
//后台任务到期,取消任务
[application endBackgroundTask:self.backgroundTaskIdentifier];
self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
}];
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
//进入前台的时候取消任务
[application endBackgroundTask:self.backgroundTaskIdentifier];
self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
}
UIViewController生命周期

生命周期方法介绍
1. load、initialize、init
  • 在main函数执行前,会初始化objc运行时环境,这时会加载所有类并调用类的load方法,一般在这个方法中实现方法交换(Method Swizzle);
  • initialize方法会在类第一次收到消息之前被调用,可以用来初始化静态变量;
  • init用来在类的内存被设置好后初始化成员变量.
2. initWithCoder、initWithNibName、awakeFromNib
  • initWithCoder:反归档,如果对象是从文件解析来的就会调用。
  • awakeFromNib:从xib或者storyboard加载完毕会调用。
  • initWithNibName:使用代码创建对象的时候会调用这个方法。

用storyboard创建界面的顺序:

initialize -> initWithCoder -> awakeFromNib -> loadView

用Xib或者纯代码创建界面的顺序:

如果用init函数来初始化:

initialize -> init -> initWithNibName -> loadView

如果用initWithNibName函数来初始化:

initialize -> initWithNibName -> loadView
3. loadView
  • 当你alloc并init了一个ViewController时,这个ViewController是还没有创建view的.

  • loadView用于加载控制器管理的 view,不能直接手动调用该方法

  • UIViewController的self.view是通过懒加载方式创建的,每次调用控制器的view属性时并且view为nil时,loadView函数就会被调用.加载成功后接着调用viewDidLoad函数,如果self.view已经是非空的情况下会直接调用viewDidLoad函数。

  • 如果在loadView函数中自定义了view,那么xib、storyboard中对页面的设置会失效,因为它是在加载之后调用的,所以如果使用 Interface Builder创建view,则务必不要重写该方法

  • [super loadView]默认的逻辑:如果控制器由xib或storyboard初始化,那么会根据xib或storyboard的传入的名字来初始化view;如果没有显示的指定名称,就默认加载和控制器同名的文件;如果没有找到文件,就会创建一个空白的UIView,这个view的frame为屏幕的大小。所以在覆写该方法的时候不应该再调用父类的该方法。

4. viewDidLoad
  • 在loadView执行完成后,view将会被加载到内存这时候调用viewDidLoad主要完成界面的初始化,例如添加子控件,以及约束的初始化
5. viewWillAppear
  • 每次进入页面都会执行viewWillAppear,该方法中可以进行操作即将显示的 view。
6. updateViewConstraints
  • 在该函数中用于更新视图的约束.在控制器的view更新视图布局时,会调用updateViewConstraints函数,可以重写这个函数来更新当前视图的布局.这个函数只有在Autolayout布局的时候才会被调用。初始化约束时,最好写到init或viewDidLoad类似的函数中,updateViewConstraints适合于更新约束
7. viewWillLayoutSubviews
  • 该方法在通知控制器将要布局 view 的子控件时调用。

  • 在这个函数中布局子视图,如果用了Autolayout,那么会在viewWillLayoutSubviews和viewDidLayoutSubviews之间用Autolayout机制布局,但是需要注意的是该方法调用时,AutoLayout 未起作用。

8. viewDidLayoutSubviews
  • 控制器的子视图的布局已完成,这里获取的frame才是最正确的frame。如果用约束来布局,在该函数去设置视图的frame 是无效的。如果用frame来布局的,在该函数中去设置视图的frame是有效的。self.view在该函数中去设置frame是有效的。该方法调用时,AutoLayout 已经完成。
9. viewDidAppear
  • 该方法在控制器 view 已经添加到视图层次时被调用
10. viewWillDisappear
  • view即将消失的时候调用
11. viewDidDisappear
  • 该方法在控制器 view 已经从视图层次移除时被调用
12. dealloc
  • 用于释放自身持有的资源.
13. didReceiveMemoryWarning

当系统内存不足时,当前控制器以及所在的Navigation堆栈上的控制器都会调用didReceiveMemoryWarning函数.该函数会判断当前控制器的view是否显示在window上,如果没有会将view以及子view全部销毁.

14 UI相关代码规范
  1. ViewController init里不要调用self.view,一般在init里应该只有关键数据的初始化。
  2. 如果确实需要重写loadView,在重写的代码中只初始化view,其他的工作放在viewDidLoad方法中完成。
  3. viewDidLoad 这时候view已经有了,可以创建并添加界面上的其他子视图,以及设置这些视图的属性。
  4. viewWillAppear 这个一般在view被添加到superview之前,切换动画之前调用,一般可以用于弹出键盘,status bar和navigationbar颜色设置等。
  5. viewWillLayoutSubViews/viewDidLayoutSubviews viewDidLayoutSubviews的时候frame值已经确定了,可以在这里做一些依赖frame的操作
  6. viewDidAppear 这时候view已经被加入到superview上了。

下面是View加载的流程:

下面是View卸载的流程:

UIView 的关键函数
  • didAddSubview

    Tells the view that a subview was added.

当前View添加子View的时候会被调用

The default implementation of this method does nothing. Subclasses can override it to perform additional actions when subviews are added. This method is called in response to adding a subview using any of the relevant view methods.

- (void)didAddSubview:(UIView *)subview;
  • willMoveToSuperview

Tells the view that its superview is about to change to the specified superview.

在某个View的SuperView改变的时候会调用这个方法

The default implementation of this method does nothing. Subclasses can override it to perform additional actions whenever the superview changes.

- (void)willMoveToSuperview:(UIView *)newSuperview;
  • didMoveToSuperview

Tells the view that its superview changed.

在view已经添加到superview上的时候被调用

The default implementation of this method does nothing. Subclasses can override it to perform additional actions whenever the superview changes.

- (void)didMoveToSuperview;
  • willMoveToWindow

Tells the view that its window object is about to change.

在view的window将要发生改变的时候调用,这个在willDisapear之后的阶段

The default implementation of this method does nothing. Subclasses can override it to perform additional actions whenever the window changes.

- (void)willMoveToWindow:(UIWindow *)newWindow;
  • didMoveToWindow

Tells the view that its window object changed.

在view的window发生改变的时候调用,这个在willDisapear之后的阶段

The default implementation of this method does nothing. Subclasses can override it to perform additional actions whenever the window changes.

- (void)didMoveToWindow;
  • willRemoveSubview

Tells the view that a subview is about to be removed.

在子view将要被移除的时候调用

The default implementation of this method does nothing. Subclasses can override it to perform additional actions whenever subviews are removed. This method is called when the subview’s superview changes or when the subview is removed from the view hierarchy completely.

- (void)willRemoveSubview:(UIView *)subview;
  • removeFromSuperview

Unlinks the view from its superview and its window, and removes it from the responder chain.

将当前View从父View和它的window,以及响应链移除

If the view’s superview is not nil, the superview releases the view.Calling this method removes any constraints that refer to the view you are removing, or that refer to any view in the subtree of the view you are removing.

- (void)removeFromSuperview;

下面是在界面上添加一个view 然后在view的各个关键节点上加上log输出的结果:

-[IDLViewController viewDidLoad] :@@@==viewDidLoad
-[IDLQuarzCoreSampleView willMoveToSuperview:] :@@@======================================willMoveToSuperview:
-[IDLQuarzCoreSampleView didMoveToSuperview] :@@@======================================didMoveToSuperview
-[IDLViewController onViewWillAppear:] :@@@==onViewWillAppear
-[IDLQuarzCoreSampleView willMoveToWindow:] :@@@======================================willMoveToWindow:
-[IDLQuarzCoreSampleView didMoveToWindow] :@@@======================================didMoveToWindow
-[IDLViewController updateViewConstraints] :@@@==updateViewConstraints
-[IDLViewController viewWillLayoutSubviews] :@@@==viewWillLayoutSubviews
-[IDLViewController viewDidLayoutSubviews] :@@@==viewDidLayoutSubviews
-[IDLViewController viewWillLayoutSubviews] :@@@==viewWillLayoutSubviews
-[IDLViewController viewDidLayoutSubviews] :@@@==viewDidLayoutSubviews
-[IDLQuarzCoreSampleView layoutSubviews] :@@@======================================layoutSubviews
-[IDLQuarzCoreSampleView drawRect:] :@@@======================================drawRect:
-[IDLViewController onViewDidAppear:] :@@@==onViewDidAppear

下面是让viewcontroller不可见时候的调用log输出:

-[IDLViewController onViewWillDisappear:] :@@@==onViewWillDisappear
-[IDLQuarzCoreSampleView willMoveToWindow:] :@@@======================================willMoveToWindow:
-[IDLQuarzCoreSampleView didMoveToWindow] :@@@======================================didMoveToWindow
-[IDLViewController onViewDidDisappear:] :@@@==onViewDidDisappear

从上面结果我们做个总结:

  1. 在ViewController viewDidLoad 之后,我们会在父view调用addSubView,将当前view添加到superView上面,这时候会调用当前view的willMoveToSuperview,didMoveToSuperview方法,这之后再去寻找自己的subView并依次添加。在添加子view的过程中didAddSubview会被反复调用。等到所有的subView都在内存层面加载完成了,会调用一次viewWillAppear,这时候会调用当前view的willMoveToWindow,didMoveToWindow然后会把加载好的一层层view,分别绘制到window上面。然后layoutSubview,drawRect,最后onViewDidAppear。

  2. UIView生命周期移除阶段。会先调用willMoveToWindow表示当前view将会从window上移除,继而调用didMoveToWindow,这里需要注意的是这时候传入的window为nil,然后再removeFromSuperView,dealloc,dealloc之后再removeSubview。

  3. 如果没有子视图,则不会接收到didAddSubview和willRemoveSubview消息。

  4. 和superView,window相关的方法,可以通过参数判断是创建还是销毁,如果指针不为空,是创建,如果为空,就是销毁。

  5. removeFromSuperview和dealloc在view的生命周期中,调且只调用一次,可以用来removeObserver,移除计时器等操作。

UIView约束,布局,绘图介绍iOS渲染的那篇博客已经介绍过了,但是这里还是把这块再拎出来介绍下,毕竟不是很大块。

  • 约束相关

  • 监控约束变化 : APP启动后,随着RunLoop的运行,系统在其内部监听着约束变化(Constraints Change):如激活或失效约束、修改优先级、修改常量值,添加,删除视图等操作,都可以导致约束发生变化。

  • 重新计算布局 : 在接收到布局变化后,布局引擎会根据变化的约束重新计算布局,并通过对其父视图调用setNeedsLayout方法将需要更新布局的视图进行标记,之后便进入延迟布局阶段(Deffered Layout Pass)。这里需要注意的是在进入延迟布局阶段之前,Layout Engine已经将更新的约束计算完毕并将视图的新frame求出。但并不在此时更新视图。

  • 延迟布局阶段 :此阶段的主要作用是将错误位置的视图重新定位。其在视图层级中执行,分为两步:

  • 更新约束:从下往上(子视图到父视图),依次遍历视图层级,调用View的updateConstraints方法(或ViewController的updateViewConstraints方法)来更新约束,我们可以在此覆盖本方法来设置自定义约束,且在此设置时,执行效率最高。记得最后调用父类实现。

  • 给视图及子视图重新设定位置(给view的frame赋值):从上到下依次调用View的layoutSubViews方法(或ViewController的viewLayoutSubViews方法),从Layout Engine中取出预算好的frame进行赋值。

* setNeedsUpdateConstraints:标记需要updateConstraints。
* updateConstraintsIfNeeded:若需要,马上调用updateConstraints应用约束更新。
* needsUpdateConstraints:返回是否需要updateConstraints。
* updateConstraints:更新约束,在这里一般只针对某个view的现有约束进行更新,约束的初始化一般放在viewDidLoad方法中,注意:要在最后调用[super updateConstraints]
  • 布局相关
* layoutSubviews:在需要进行布局的时候会调用这个方法,我们可以通过覆写这个方法来确定每个view的frame。

手动触发layoutSubviews:

* setNeedsLayout:此方法会将view当前的layout设置为无效的,并在下一个upadte cycle里去触发layout更新。 * layoutIfNeeded:使用此方法强制立即进行layout,从当前view开始,此方法会遍历整个view层次(包括superviews)请求layout。因此,调用此方法会强制整个view层次布局。

哪些条件下会自动触发layoutSubviews:

1、init初始化不会触发layoutSubviews,但是用initWithFrame进行初始化的时候,当rect的值不是CGRectZero时也会触发 2、addSubview会触发layoutSubviews。 3、设置view的frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化。 4、改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件。 5、滚动一个UIScrollView会触发layoutSubviews。 6、旋转屏幕会触发父UIView上的layoutSubviews事件。

* ****绘制相关****

* setNeedsDisplay:标记整个视图的边界矩形需要重绘. * drawRect:如果你的View画自定义的内容,就要实现该方法。若使用UIView绘图,只能在drawRect:方法中获取相应的ContextRef并绘图,其他方法中获取将获取到一个invalidate 的ContextRef不能用于绘制。 * 若使用CAlayer绘图,只能在drawInContext: 中绘制,或者在Delegate中的相应方法绘制.

哪些条件下会自动触发drawRect:

* 如果在UIView初始化时没有设置rect大小将直接导致drawRect不被自动调用。drawRect 调用是在loadView,viewDidLoad 两方法之后掉用的.所以可以在控制器中设置一些在draw阶段需要的值。 * 该方法在调用sizeToFit后被调用,所以可以先调用sizeToFit计算出size。然后系统自动调用drawRect:方法。 * 通过设置contentMode属性值为UIViewContentModeRedraw。那么将在每次设置或更改frame的时候自动调用drawRect:。 * 直接调用setNeedsDisplay,或者setNeedsDisplayInRect:触发drawRect:但是有个前提条件是rect不能为0。

* 这些方法在UIViewController生命周期的位置:
在一个UIView init初始化后会把三个标记都置为YES。然后在VC的布局方式中,viewWillLayoutSubviews中会调用updateConstraints,在viewDidLayoutSubviews会调用layoutSubviews,drawRect。

其中updateConstraints是子 -> 父。layoutSubviews和drawRect是父 -> 子。

* sizeToFit

* sizeToFit会自动调用sizeThatFits方法; * sizeToFit不应该在子类中被重写,应该重写sizeThatFits * sizeThatFits传入的参数是receiver当前的size,返回一个适合的size * sizeToFit可以被手动直接调用 * sizeToFit和sizeThatFits方法都没有递归,只负责自己 ``` 最后来一张总结的图,来自之前的一篇博客《图解生命周期》 ![](/iOS-生命周期/000015.png) ###### TODO App 保活方案
Contents
  1. 1. App 生命周期
    1. 1.1. 应用程序的五种状态
    2. 1.2. AppDelegate
    3. 1.3. 一些关键场景下的状态及回调
    4. 1.4. 如何优化性能
  2. 2. App后台限制
  3. 3. UIViewController生命周期
    1. 3.1. 生命周期方法介绍
    2. 3.2. 1. load、initialize、init
    3. 3.3. 2. initWithCoder、initWithNibName、awakeFromNib
    4. 3.4. 3. loadView
    5. 3.5. 4. viewDidLoad
    6. 3.6. 5. viewWillAppear
    7. 3.7. 6. updateViewConstraints
    8. 3.8. 7. viewWillLayoutSubviews
    9. 3.9. 8. viewDidLayoutSubviews
    10. 3.10. 9. viewDidAppear
    11. 3.11. 10. viewWillDisappear
    12. 3.12. 11. viewDidDisappear
    13. 3.13. 12. dealloc
    14. 3.14. 13. didReceiveMemoryWarning
    15. 3.15. 14 UI相关代码规范
  4. 4. UIView 的关键函数