为什么作者在示例 2 中使用强制展开,而在示例 1 中不使用?

Why does the author use forced unwrapping in Example 2 but not in example one?

我知道 optionalsforced unwrapping 的概念,但只是引用 iOS 8 Swift Programming Cookbook 中的一个例子,我不知道不明白为什么要用 var imageView: UIImageView 在示例 1 中,但在示例 2 中强制解包 var imageView: UIImageView!。希望有人告诉我我在这里缺少什么,以便我知道要阅读什么。

示例 1:

class ViewController: UIViewController {
    let image = UIImage(named: "Safari")
    var imageView: UIImageView

    required init(coder aDecoder: NSCoder) {
        imageView = UIImageView(image: image)
        super.init(coder: aDecoder)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        imageView.center = view.center
        view.addSubview(imageView)
    }
}

示例 2:

import UIKit

class ViewController: UIViewController {
    let image = UIImage(named: "Safari")
    var imageView: UIImageView!

    override func viewDidLoad() {
        super.viewDidLoad()
        imageView = UIImageView(frame: view.bounds)
        imageView.image = image
        imageView.center = view.center
        view.addSubview(imageView)
    }
}

在示例1中,变量值设置在init中。在示例 2 中,变量稍后被初始化,并且在初始化之后(直到 viewDidLoad 被调用)它的值是 nil.

因为在示例 1 中变量永远不会 nil,它可以是非可选的。但是,在示例2中变量很长一段时间是nil,所以它必须是一个可选变量。

在 swift 中,所有非可选 class 和结构属性都必须在构造函数中初始化,并且没有办法避免这种情况(除非它是内联初始化的,连同它的声明)。

在某些情况下,非可选 属性 不能在实例化时初始化,因为它依赖于相同 class 的其他属性,或者它必须在稍后阶段初始化实例生命周期。因此,这些属性必须声明为可选的。

解决方法包括将所有属性声明为隐式解包 - 在内部它们是可选的,但它们作为非可选的访问(即没有 ? 运算符)。

这解释了示例 1 和示例 2 之间的区别:在前者中,属性 在初始化程序中被初始化,因此它被声明为非可选的。在后一种情况下,它是在 viewDidLoad 方法中初始化的,因此它被声明为隐式解包。

这种模式在视图和视图控制器插座中广泛使用 - 您会注意到,当您在 IB 中创建插座时,相应的 属性 是隐式展开的 - 这是因为变量是在初始化后分配的。

正如其他人所指出的,这两个示例的目的是说明在初始化过程中设置的非可选变量与可选变量(在本例中为隐式展开的变量)之间的区别稍后实例化。

值得注意的是,虽然此示例具有教学价值,但从实际角度来看,在处理视图控制器时,您将使用后一种模式,并在 viewDidLoad 中设置可选变量(或者对于以编程方式创建的视图,您可能会使用第三种模式,即 loadView 方法),而不是第一种在 init 中实例化非可选模式的模式。

我只是想确保您没有放弃这个示例,并得出结论,在视图控制器的情况下,您应该自由地使用这两种模式中的任何一种。有一些原因与 Swift 语言无关,但与视图控制器、视图等的生命周期细节有关,在处理视图控制器时,您更喜欢 viewDidload 中设置的可选变量.

尽管如此,重要的是要了解在 Swift 中哪些情况下会使用可选,哪些情况下不会(关键点是如果变量可能不是,则必须使用可选在对象的初始化过程中设置),所以从这个角度来看,这可能是一个有启发性的例子。但请记住,在视图控制器的特定情况下,通常会采用 viewDidLoad 模式。

示例 1 的唯一初始化程序是 required init(coder aDecoder: NSCoder),它总是为 imageView 赋值。因此 imageView 在初始化发生后总是有一个值。

示例 2 没有或不需要启动器,因为两个存储的属性在它们的声明中都被赋予了初始值:

let image = UIImage(named: "Safari") // has an initial value of whatever UIImage(named: "Safari") returns
var imageView: UIImageView! // has an initial value of nil

因此,您可以构造一个 ViewController,方法是调用 swift 自动添加的初始化程序:ViewController()

这将导致 imageView 的值为 nil