CS193笔记 第六讲 多重MVC,控制器的生命周期和内存管理

我开始用Markdown方式来记笔记了。现在第十一讲的视频都已经放出来了。为了提速,内容会记得比较简单,以记录核心内容为主,后面再逐步进行补充。对本讲有任何疑问,请留言,我会优先进行更新。

<h4>本讲简介:</h4> 本讲通过演示来介绍,最后几分钟时间介绍了内存管理,主要是对引用类型对象的管理。

演示:
为FaceIt增加 Master-Detail View

Navigation View

View Controller 的生命周期

Instantiated
awakeFromNib
segue preparation happens
outlets get set
viewDidLoad
viewWillAppear
viewWillLayoutSubviews
viewDidLayoutSubviews
viewWillDisappear

内存管理
简单来讲,内存管理是自动的或对开发者透明的。

ARC vs MRC vs GC
自动引用计数
手动引用计数
垃圾回收技术

Strong Weak unOwned

闭包 (Closures)

Closures: 一等公民,是引用类型,居住在堆上
<pre>addUnaryOperation("✅", operation: { (x: Double) -> Double in
display.textColor = UIColor.green
return sqrt(x)
} )</pre>

<pre>addUnaryOperation("✅") {
self.display.textColor = UIColor.green
return sqrt($0)
} </pre>

<pre>addUnaryOperation("✅") { [ unowned me = self ] in
me.display.textColor = UIColor.green
return sqrt($0)
} </pre>

这个例子有几个知识点:
1,闭包做为最后一个参数的简化写法
2,介绍了一种可能产生内存引用互锁的情况
3,引入一个unowned 变量 打破循环

更容易想到的打破循环引用的
<pre>addUnaryOperation("✅") { [ weak weakSelf = self ] in
weakSelf?.display.textColor = UIColor.green
return sqrt($0)
} </pre>

但还可以这样写

<pre>
addUnaryOperation("✅") { [ weak self ] in
self?.display.textColor = UIColor.green
return sqrt($0)
}
</pre>

第十三讲结尾介绍了一个需要避免memory cycle 的情况

@escaping 用于修饰闭包。闭包默认是非逃逸的。@escaping修饰表示逃逸,即闭包的执行会产生对闭包外部的访问,或某种形式上的副效果。在Swift 3以前,闭包默认是逃逸的, 非逃逸闭包要用@noescaping修饰。

<h4>再谈!</h4>
上一讲里面提到过,因为在声明faceView时使用了!,在使用它时是不需要加?的。但在本讲中,如果在下面访问faceView时不加?,应用会出错。

<pre>
//FaceViewController.swift
@IBOutlet weak var faceView: FaceView!

private func updateUI() {
    switch  expression.eyes {
    case .open:
        faceView?.eyesOpen = true
    case .closed:
        faceView?.eyesOpen = false
    case .squinting:
       faceView?.eyesOpen = false
    }
    faceView?.mouthCurvature = mouthCurvature[expression.mouth] ?? 0.0
}</ol>

</pre>

原因在于,Segue总会建立新的MVC实例。而在 prepare(for segue) 中<code> faceViewController.expression = expression </code>引发了对上面的updateUI()的调用,而此时faceView还没有被建立,仍然为nil。所以需要加?。这说明使用!后,只是对编译器来说可以接受没有unwrapping。但像本例的情况仍然需要。

<pre>
<ol> //EmotionsViewController.swift
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationViewController = segue.destination
if let faceViewController = destinationViewController as? FaceViewController,
let identifier = segue.identifier,
let expression = emotionalFaces[identifier] {
faceViewController.expression = expression
}
}<ol>
</pre>

<h4>课后练习</h4>
<p>在增加了navigation Controller 后,应用启动后直接显示了 Face View。老师留了一个练习,是希望启动后显示带有三个按钮的emotion 选择页面。老师还给了个提示,要用到后面讲的delegate的内容。在这儿我们自己实现一下。</p>
<ol><li>打开帮助文档,在输入框敲入 Navig,这时提示列表就回显示出一些推荐的结果,选取 navigationController(_:willShow:animated:)
<li>根据帮助内容我们确定这个方法会在每一页显示前被调用。而且从帮助页面的顶部我们可以知道这个方法属于协议 UINavigationControllerDelegate
<li>创建一个新的 Cocoa Touch Class 文件,将其命名为EmotionsNavViewController,父类先不用动,后面再改。
<li>打开Main.storyboard, 选中Navigation Controller 查看 identity inspector 中的 class 为 UINavigationController。因而我们打开新建的文件,将
<pre><code>class EmotionsNavViewController: ... { </code></pre> 替换为
<pre><code>class EmotionsNavViewController: UINavigationController, UINavigationControllerDelegate {</code></pre>回到 identity inspector 中将Navigation Controller 的 class 的值改为 EmotionsNavViewController
<li>在类中添加我们在第1步找到的方法
<pre> func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
}</pre><li>在这儿我们该怎么做呢?查看UINavigationController的帮助
“A navigation controller coordinates its behavior with its delegate object. The delegate object can override the pushing or popping of a view controller ... "
我们看到了 override,所以有戏。我们在willShow 方法中加入
<pre><code>popViewController(animated: true)</code></pre>运行一下,没有效果。在新加的代码上设一个断点,会发现根本没有被调用。
<li>在viewDidLoad()里面加入
<pre><code>delegate = self</code></pre>再次执行程序,成功地显示了我们想要的页面。
<li>点一下 sad 按钮,oops! FaceView 出现后又收回了。看来willShow中的代码还在起作用。增加一个属性
<pre>private var displayed = false</pre>然后将 willShow的内容改为
<pre>if displayed == false {
displayed = true
popViewController(animated: true)
}</pre> command+R 运行程序。一切OK。</ol>

官方答案在第八讲。

推荐阅读更多精彩内容