探究 UIViewController 生命周期

因为种种缘由,掘金等第三方平台博客再也不保证可以同步更新,欢迎移步 GitHub:github.com/kingcos/Per…。谢谢!git

Lifecycle of UIViewController in iOSgithub

Date Notes Swift Xcode
2017-03-10 首次提交 3.0 8.2.1

前言

对象的生命周期一直是开发者所须要关心的,教授 CS193p 的老师 Paul 也详细的讲述了 UIViewController 的生命周期。为了记述这一过程,故做此文。因为 Xcode 提供了纯代码和 Storyboard(Xib 同理)两种布局 UI 的方式,所以初始化部分略有不一样。安全

为了方便观察,我建立了一个 BaseViewController,继承自本来的 UIViewController,重写其中的生命周期方法,并让后续新的控制器继承自该控制器,以便观察。app

本文对应的 Demo 能够在 github.com/kingcos/UIV… 查看、下载。iview

Structure

Initialization

Storyboard

OUTPUT: init(coder:) awakeFromNib()ide

init(coder:)

  • 当使用 Storyboard 时,控制器的构造器为 init(coder:)
  • 该构造器为必需构造器,若是重写其余构造器,则必须重写该构造器。
  • 该构造器为可失败构造器,即有可能构造失败,返回 nil。
  • 该方法来源自 NSCoding 协议,而 UIViewController 听从这一协议。
  • 该方法被调用意味着控制器有可能(并不是必定)在将来会显示。
  • 在控制器生命周期中,该方法只会被调用一次。

awakeFromNib()

  • 当使用 Storyboard 时,该方法会被调用。
  • 当调用该方法时,将保证全部的 outlet 和 action 链接已经完成。
  • 该方法内部必须调用父类该方法,虽然默认实现为空,但 UIKit 中许多类的该方法为非空。
  • 因为控制器中对象的初始化顺序不能肯定,因此构造器中不该该向其余对象发送消息,而应当在 awakeFromNib() 中安全地发送。
  • 一般使用 awakeFromNib() 能够进行在设计时没法完成的必要额外设置。

Code

OUTPUT: init(nibName:bundle:) - NibName: nil, Bundle: niloop

init(nibName:bundle:)

  • 当使用纯代码建立控制器,控制器的构造器为 init(nibName:bundle:)
  • 虽然使用代码建立时调用了该构造器,但传入的参数均为 nil。

OUTPUT: loadView() viewDidLoad() viewWillAppear viewWillLayoutSubviews() - Optional((162.0, 308.0, 50.0, 50.0)) viewDidLayoutSubviews() - Optional((67.0, 269.0, 241.0, 129.0)) viewDidAppear viewWillDisappear viewDidDisappear deinit布局

loadView()

  • loadView() 即加载控制器管理的 view。
  • 不能直接手动调用该方法;当 view 被请求却为 nil 时,该方法加载并建立 view。
  • 若控制器有关联的 Nib 文件,该方法会从 Nib 文件中加载 view;若是没有,则建立空白 UIView 对象。
  • 若是使用 Interface Builder 建立 view,则务必不要重写该方法。
  • 可使用该方法手动建立视图,且须要将根视图分配为 view;自定义实现不该该再调用父类的该方法。
  • 执行其余初始化操做,建议放在 viewDidLoad() 中。

viewDidLoad()

  • view 被加载到内存后调用 viewDidLoad()
  • 重写该方法须要首先调用父类该方法。
  • 该方法中能够额外初始化控件,例如添加子控件,添加约束。
  • 该方法被调用意味着控制器有可能(并不是必定)在将来会显示。
  • 在控制器生命周期中,该方法只会被调用一次。

viewWillAppear(_:)

  • 该方法在控制器 view 即将添加到视图层次时以及展现 view 时全部动画配置前被调用。
  • 重写该方法须要首先调用父类该方法。
  • 该方法中能够进行操做即将显示的 view,例如改变状态栏的取向,类型。
  • 该方法被调用意味着控制器将必定会显示。
  • 在控制器生命周期中,该方法可能会被屡次调用。

注意: 若是控制器 A 被展现在另外一个控制器 B 的 popover 中,那么控制器 B 不会调用该方法,直到控制器 A 清除。动画

viewWillLayoutSubviews()

  • 该方法在通知控制器将要布局 view 的子控件时调用。
  • 每当视图的 bounds 改变,view 将调整其子控件位置。
  • 该方法可重写以在 view 布局子控件前作出改变。
  • 该方法的默认实现为空。
  • 该方法调用时,AutoLayout 未起做用。
  • 在控制器生命周期中,该方法可能会被屡次调用。

viewDidLayoutSubviews()

  • 该方法在通知控制器已经布局 view 的子控件时调用。
  • 该方法可重写以在 view 布局子控件后作出改变。
  • 该方法的默认实现为空。
  • 该方法调用时,AutoLayout 已经完成。
  • 在控制器生命周期中,该方法可能会被屡次调用。

viewDidAppear(_:)

  • 该方法在控制器 view 已经添加到视图层次时被调用。
  • 重写该方法须要首先调用父类该方法。
  • 该方法可重写以进行有关正在展现的视图操做。
  • 在控制器生命周期中,该方法可能会被屡次调用。

viewWillDisappear(_:)

  • 该方法在控制器 view 将要从视图层次移除时被调用。
  • 相似 viewWillAppear(_:)
  • 该方法可重写以提交变动,取消视图第一响应者状态。

viewDidDisappear(_:)

  • 该方法在控制器 view 已经从视图层次移除时被调用。
  • 相似 viewDidAppear(_:)
  • 该方法可重写以清除或隐藏控件。

didReceiveMemoryWarning()

  • 当内存预警时,该方法被调用。
  • 不能直接手动调用该方法。
  • 该方法可重写以释放资源、内存。

deinit

  • 控制器销毁时(离开堆),调用该方法。

Note

Rotation

OUTPUT: willTransition(to:with:) viewWillLayoutSubviews() - Optional((67.5, 269.5, 240.0, 128.0)) viewDidLayoutSubviews() - Optional((213.5, 123.5, 240.0, 128.0)) viewWillLayoutSubviews() - Optional((213.5, 123.5, 240.0, 128.0)) viewDidLayoutSubviews() - Optional((213.5, 123.5, 240.0, 128.0)) viewWillLayoutSubviews() - Optional((213.5, 123.5, 240.0, 128.0)) viewDidLayoutSubviews() - Optional((213.5, 123.5, 240.0, 128.0))ui

  • 当 view 转变,会调用 willTransition(to:with:) 方法。
  • 当屏幕旋转,view 的 bounds 改变,其内部的子控件也须要按照约束调整为新的位置,所以也调用了 viewWillLayoutSubviews()viewDidLayoutSubviews()

Present & Dismiss

OUTPUT: viewWillDisappear viewDidDisappear viewDidDisappear viewWillAppear viewDidAppear

  • 当在一个控制器内 Present 新的控制器,原先的控制器并不会销毁,但会消失,所以调用了 viewWillDisappearviewDidDisappear 方法。
  • 若是新的控制器 Dismiss,即清除本身,原先的控制器会再一次出现,所以调用了其中的 viewWillAppearviewDidAppear 方法。

死循环

class LoopViewController: UIViewController {

    override func loadView() {
        print(#function)
    }

    override func viewDidLoad() {
        print(#function)
        let _ = view
    }

}
复制代码

OUTPUT: loadView() viewDidLoad() loadView() viewDidLoad() loadView() viewDidLoad() loadView() viewDidLoad() loadView()

  • loadView() 没有加载 view,viewDidLoad() 会一直调用 loadView() 加载 view,所以构成了死循环,程序即卡死。

Reference

也欢迎您关注个人微博 @萌面大道V & 简书

相关文章
相关标签/搜索