隐式解包可选类型:既然我们知道值存在,为什么还需要它们?

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

既然我们已经知道值存在,为什么 Swift 中还需要隐式解包可选类型?



你是否曾经在阅读他人的 Swift 代码时,看到变量声明中带有感叹号而感到困惑?var name: String! — 这不仅仅是一个可选类型,而是隐式解包可选类型(IUO)。这时一个合理的问题出现了:“如果你确定值存在,为什么要创建这样的东西?为什么不直接使用普通的 String?”



这个问题困扰着成千上万的开发者,在 Stack Overflow 上它已经获得了数万次浏览。今天我们将一劳永逸地弄清楚为什么需要 IUO,何时使用它们,以及何时最好远离它们。开始吧!



可选类型快速回顾(针对那些已经忘记的人)



在深入 IUO 之前,我们先回顾一下什么是普通的可选类型。在 Swift 中,一个可能为 nil 的变量必须声明为可选类型:



var maybeNumber: Int? = 42maybeNumber = nil // 这是正常的


要使用该值,需要解包(unwrap)——要么通过 if let、guard let,要么通过强制解包(force unwrap):



print(maybeNumber!) // 危险!如果为 nil 则会崩溃


现在想象一下,你绝对确定变量在访问时永远不会是 nil,但由于某种原因,你无法在初始化时为其赋值。这时隐式解包可选类型就出现了。



什么是隐式解包可选类型?



隐式解包可选类型是一种可选类型,在访问时会自动解包。你使用 ! 而不是 ? 来声明它:



var name: String! = "Alice"print(name) // "Alice" — 自动解包了!


请注意:我们不需要写 name!。系统会自动为我们做这件事。然而,如果在访问时 name 是 nil,应用程序将会崩溃并报错——就像强制解包一样。



IUO 是对编译器的一个承诺:“我知道我在做什么。这个值会存在,即使现在还没有。” 这是在可选类型的安全性和普通类型的便利性之间的一种折衷。



IUO 的主要使用场景



1. Interface Builder 中的 Outlets(UIKit)



最常见的例子是 iOS 开发中的 @IBOutlet。当你在代码和 Storyboard 之间建立连接时,outlet 无法在 init 中初始化,因为视图尚未加载。但在加载后(viewDidLoad),你确切知道 outlet 存在。以前(在 Swift 3 之前),所有 outlets 都声明为 IUO:



@IBOutlet var label: UILabel!


为什么不用 UILabel??因为那样每次访问 label 时都必须写 label! 或使用 guard let。这很繁琐且使代码变得杂乱。IUO 使代码更简洁,同时保持安全性(如果你不小心忘记连接 outlet,崩溃会立即发生,你可以快速找到错误)。



重要提示: 从 Xcode 9 和 Swift 4 开始,Apple 建议对 outlets 使用普通的可选类型 UILabel?。但许多项目仍然使用 IUO,并且这仍然有效。



2. 两阶段初始化(Two-Phase Initialization)



有时你无法在初始化时为属性赋值,但保证在首次使用之前它会被设置。一个典型的例子是在调用另一个方法后设置的依赖项:



class NetworkManager {    var apiKey: String!        func configure(with key: String) {        self.apiKey = key    }        func fetchData() {        // 这里 apiKey 肯定不是 nil        print("Fetching                

博客

书籍推荐