Swift 学习笔记5 - Segue & View Controller的生命周期

前言

这是斯坦福大学的课程-Developing iOS 9 APPs with Swift,第6节课的内容。主要是讲:组合多个 MVC (Multiple MVCs) 和 View Controller 的生命周期。在 Swift 学习笔记3中我也提到过一些 Multiple MVCs 的基础知识,这篇文章算是应用进阶版。

经过这段时间的学习,个人总结了3个非常好的学习 swift 基础的途径资料(列在下方)。其它的资料比如各路大神的书籍和博客我作为辅助学习,比如喵神,YYKit 的作者 ibireme, 等等。进阶的资料,我觉得学习 Github 中的各种开源项目是个非常好的选择,比如喵神的 Kingfisher 等。当然如果大家能订阅我的博客,我会非常开心哒。:)

下面是我用的最多的3个 Swift 基础学习资料:

  • 斯坦福的这个课程,Developing iOS 9 Apps with Swift.
  • 苹果官方文档,里面也有实例教程。Learn by doing 永远是最好的学习方法。
  • 按住 option 点击代码中的任一单词。这个大家应该都知道,但我是真心越来越觉得这个太好用了。

Demo

又是这张萌蠢的脸,但这次它不仅换了发型,还添加了按钮。感兴趣的童鞋可以去我的 Github 查看源码。

Swift 学习笔记5 - Segue & View Controller的生命周期_第1张图片

Segue

首先介绍一个名词 - segue。简短的翻译就是:转换。

我们创建了很多 Controller 的 Controller,我们需要用一个 MVC 触发另一个 MVC,这种 MVC 之间的转换就是 segue,一般不用 transition,而用 segue,之后会经常遇到。

主要有4种 segue:

  • Show Segue(比如在 Navigation Controller 中,一个 MVC 出现在另一个 MVC 里。)
  • Show Detail Segue(比如我们这次的 Demo,一个 master,一个 detail,那么就需要用到这个 show detail。Navigation Controller 中也可以使用 show detail segue,和 show segue 功能一样。)
  • Modal Segue(占据整个屏幕)
  • Popover Segue(出现一个小弹窗)

Segue 会创建一个新的实例(后文也会继续提到)。就是说每次点击同样的按钮,它返回的不是之前的那个界面,虽然长的也许一样,但其实是一个全新的界面,是重新创建的。

必须要给新建的 segue 一个 Identifier(在 Attributes inspector 中设置)。每个 segue 都要有自己独特的 id,因为我们要对它进行操作。比如我们可以使用 UIViewController 的方法,func performSegueWithIdentifier(identifier: String, sender: Anyobject?) 启用 segue,但我们几乎不用这种方法,一般都在storyboard 中直接用 ctrl 拉线。segue 的 id 更重要的一个用途是:preparing for a segue。用代码来说就是:

func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { // sender 一般是 button,但可以是任何东西
  if let identifier = segue.identifier { // 检查是否 nil
    switch identifier {
      case "Show Graph":
        if let vc = segue.destinationViewController as? GraphController { // 如果非 nil,则作为 GraphController
          vc.property1 = ...
          vc.CallMethodToSetItUp(...)
        }
      default: break
    }
  }
}

在上面的这段代码中,有个 switch case 语句,而我们在 Demo 中,充分发挥了 Swift 的特性,使用了字典,使得我们的代码更加优雅(这也是我为什么喜欢 Swift 的原因,相比于 CPP)。

private let emotionalFaces: Dictionary = [

注意:这个准备(preparation)的过程是在设置好 outlet 之前完成的,其实也挺符合实际,因为先把该准备的东西准备好了,才能去设置再去呈现。但在实际开发过程中很容易出现 bug,不自觉的就会去使用未设置好的 optional 变量。文末会介绍这样的 bug 以及相应的处理对策。

我们也可以阻止 segue,只要在 UIViewController 中实现下面这个方法,返回 false。

func shouldPerformSegueWithIdentifier(identifier: String?, sender: AnyObject?) -> Bool

组合多个 MVC

有3种方式可以将多个 MVC 联合起来,

  1. Tab Bar Controller:
    每个界面应该是相互独立的,因为如果两个界面相互关联,如上面提到的我们这次的 Demo,我们希望用户点击了某个相应的事件按钮才能看到下个界面,而不是直接点了和事件无关的 Tab Bar 就能看到下个界面。所以我们的 Demo 不能用 Tab Bar Controller。
  2. Split Controller:
    如我在 Swift 学习笔记3 - Gesture中有提到的, “对于一个split view来说,[0]是主体部分, [1]是细节部分”。对于我们这次的 Demo,带有 “Angry”, "Worried" 等按钮的界面可以是主体部分 (master),而那个呆萌的脸可以作为呈现细节的另一个view (detail)。 别忘记 Split Controller 是用于 ipad 的(以及6 plus),那是因为 6 plus 以前的 iphone 屏幕不够大,没办法 split。要想在 6 里用 split,我们还是需要借助下面的这个哥们,Navigation Controller.
  3. Navigation Controller:
    选中主体部分(master),然后 Editor -> Embed in -> Navigation Controller。Boom!It worked! 其实它就是多了个 Back 按钮,这也是 Navigation Controller 精髓所在。在之前的学习笔记中也说过,Navigation Controller 是通过 stack 的方式存放这些 view 的,Back 就代表将现在的这个 view 抛弃掉(是彻底抛弃哦,重新点击按钮,它创建的就是一个新的界面,和之前的那个毛线关系没有啦),然后返回上一个 view。但如果是 Tab Bar,它就是一直存在的,也就是说点击一个 tab A,再点 B,再点 A,它出现的还是原来的 A。用教授的话说就是 “tab bar 不是 segue,navigation 是 segue”。
  • 有一点需要注意的是,如果在一个 Navigation Controller 中放有另一个 Navigation Controller,iOS 会自动忽视里面的那个 Navigation Controller。
  • 当我们想在 detail 的那个界面加个标题,我们还需要在那个 detail 上 embed 一个 Navigation Controller,但是这样的话在 ipad 上运行那些按钮就没用了,因为 prepare 的是 UINavigationController, 而我们想要 prepare 的是 UINavigationController 里面的内容,也就是我们之前做好的 detail view 里的东西。所以我们还需要加上这段代码,也就是 prepare UINavigationController 里面的内容的。
if let navcon = destinationvc as? UINavigationController {
            destinationvc = navcon.visibleViewController ?? destinationvc
        }

View Controller 生命周期

生命周期大概是这样的:

  • Creation. 一般在 storyboard 中创建实例。
  • Preparation if being segued to.
    如之前所说,这个准备过程在 Outlet Setting 之前完成。
  • Outlet Setting.
  • Appearing and disappearing.
  • Geometry changes.
  • Low-memory situation. 一般在现在的 iphone 中很少发生。但要是发生了,可以释放好久不用的占用很大空间的事件,比如最长未使用原则(LRU)。

在 storyboard 中创建实例,设置了 outlet 之后,就是调用 viewDidLoad 方法了。viewDidLoad, viewWillLoad, viewWillAppear, viewWillDisappear, viewDidAppear, viewDidDisappear 等等这么多方法,我会在下面逐条整理。

  • viewDidLoad

    view 只会 load 一次,一般在这里在这里进行一些初始化的工作,我们一般不用 init 方法,因为此时 outlet 已经设置完毕了。我们一般也将 update UI 的工作放在这个方法里。但是 view 的 geometry 不在这里设置,因为还不知道使用的设备是什么。这里的 geometry 的意思是 view 的大小尺寸,横向还是纵向之类的。
override func viewDidLoad() {
 super.viewDidLoad() 
 // 进行一些 MVC 初始化的工作
}
  • viewWillAppear

    这是 view 即将呈现出来之前的方法,一般把需要大量运算的程序放在这里(比如多线程的操作),view 的 geometry 也是在这里设置的,但是如果要做旋转之类的操作,其它地方会响应这些操作。
override func viewDidLoad() {
 super.viewDidLoad() 
 // 进行一些 MVC 初始化的工作
}
  • viewDidAppear

    这是在 view 呈现出来之后的方法,一些动画在这里进行。
func viewDidAppear(animated: Bool)
  • viewWillDisappear

    这是 view 即将消失之前的方法,主要做一些简单的清理工作,但一些非常耗时的工作不是在这里进行的,这里可以关闭动画。
override func viewWillDisappear(animated: Bool) {
 super.viewWillDisappear(animated) // 在所有的 viewWill/Did 方法中,调用 super。
 // 进行一些 MVC 初始化的工作
}
  • viewDidDisappear

    这是在 view 消失之后的方法,释放一些在 willappear 阶段从网络上拿来的数据。
func viewDidDisappear(animated: Bool)
  • func viewWillLayoutSubviews()func viewDidLayoutSubviews(),这两个方法用于几何变换的时候(geometry),而我们在 storyboard 中相对应于那些蓝线设置的 constraints,是在这两个方法之间发生的。这里其实我们不需要做什么特殊操作,因为都是自动完成的。
  • viewWillTransitionToSize

    在做旋转变换的时候,就是将手机或者 pad 屏幕横过来的时候,我们可以在这里设置一些动画属性。
func viewWillTransitionToSize() {
  size: CGSize,
  withTransitionCoordinator: UIViewControllerTransitionCoordinator
}
  • awakeFromNib

    在 preparation 和 outlet set 之前(在 MVC load 之前)。这个会发给任何对象,不只是 ViewController。

概括这个周期,就是:

  • Instantiated (一般从 storyboard 中创建实例)
  • awakeFromNib
  • segue preparation
  • outlet set
  • viewDidLoad

    以下这些会经常调用,比如每次显示和关闭某个 view 的时候
  • viewWillAppear & viewDidAppear
  • viewWillDisappear & viewDidDisappear

    以下几何变换的方法有可能在 viewDidLoad 之后的任何时候被调用
  • viewWillLayoutSubviews
  • 自动部署布局
  • viewDidLayoutSubviews
  • didReceiveMemoryWarning (内存不够的时候)

Demo 中的 bug 和对策

有个 bug 在 iOS 开发中应该会经常遇到,就是

fatal error: unexpectedly found nil while unwrapping an Optional value

如 Paul Hegarty 教授所讲,

Your outlets are not set at the time you preparing

这是因为有个 optional 我们没有处理,比如在这个 demo 中,faceView 是 optional 的,faceView 是这样来的:

@IBOutlet weak var faceView: FaceView!

所以在后面 update faceView 的时候,需要考虑它 nil 的情况,有两种措施:

  • 第一种方法,以其中一条语句为例
faceView?.eyeBrowTilt = eyeBrowTilts[expression.eyeBrowns] ?? 0.0

注意等号左边的问号 faceView?,它可以处理 optional nil 的问题,如果式子的任何一个地方为 nil,那么它就会 ignore 整条语句。但是这样的语句很多,我们不能每条都这样处理。仅管我们是码农程序猿,我们也要学会优雅。:)

  • 另一个方法就是加上 if faceView != nil { },这样就避免了 nil 问题了。

by:诸葛俊伟
欢迎转载,转载请注明出处。访问我的个人主页,了解更多。

你可能感兴趣的:(Swift 学习笔记5 - Segue & View Controller的生命周期)