[翻译]理解Swift中的Optional

时间:2022-05-23 03:39:22

原文出处:Understanding Optionals in Swift

苹果新的Swift编程语言带来了一些新的技巧,能使软件开发比以往更方便、更安全。然而,一个很有力的特性Optional,在你第一次使用时可能会感到困惑。Optionals将会在编译阶段检查哪些值为nil。通过这种方式,你可以更好的保证应用程序交付在用户手里是可运行的。在Swift中,Optionals也提供了一些接口用来和遗留的Objective-C代码之间交互。

初试Optional

让我们在XCode中新建一个叫做swift-optionals的playground文件。你可以添加下面的代码来看看Optionals是什么样的。

import Foundation

var rightURL = NSURL(string: "http://www.reactive.io") // => {Some http://www.reactive.io}
var wrongURL = NSURL(string: "this is not a real url") // => nil

在这种场景下,我们试着通过字符串来创建NSURL。对于rightURL,我们得到了一个Some的结果,里面存放着一个NSURL对象,对于wrongURL,我们将得到nil。在两种情况下都没有直接得到NSURL,这是一件好事,因为Some结果需要显示的解包,这将迫使我们检查nil值,让我们看看这是如何工作的。

一切都是Some

如果我们在XCode中看下NSURL的构造函数,我们将会看到如下的代码:

convenience init?(string URLString: String)

那个在init之后的问号标记告诉我们当构造函数执行完毕,NSURL将会返回一个Optional。在Swift中,Optional是一个实际的类型,更加明确的说是一个泛型的enum,这种类型或者持有另一个对象,或者是nil。让我们看看Optional长什么样?

enum Optional<T> : Reflectable, NilLiteralConvertible {
case None
case Some(T) /// Construct a `nil` instance.
init() /// Construct a non-\ `nil` instance that stores `some`.
init(_ some: T)
...

我们能够看到Optional枚举包括两个case,None代表nil,Some代表具体的泛型类型。在Swift中,我们可以把任何东西装箱在Optional.Some中。

Optional<String>.Some("really good stuff") // => {Some "really good stuff"}

另一方面,None等价于nil

Optional<String>.None == nil // => true

从这个角度来讲,你可以把Optional理解为一个箱子,有可能会包含一个具体的类型,也有可能没有包含。向一个方法或者函数发送一个箱子类型而不是具体类型,编译器将会强迫你打开箱子检查箱子里面的类型。如果箱子是空的,你可以捕捉这个错误并且处理这种错误。

Swift将会推断出变量的类型,不过为了让事情变得更透明,我们显示的以两种方式来使用Optional:

import Foundation

var rightURL: Optional<NSURL> = NSURL(string: "http://www.reactive.io") // => {Some http://www.reactive.io}
var wrongURL: NSURL? = NSURL(string: "this is not a real url") // => nil

对于rightURL,我们通过一种冗长的方式来创建一个Optional类型,因为我们会大量使用Optional,Swift给了我们一种简写方式,通过在变量后面追加一个问号‘?',就如同我们之前看到的NSURL构造器那样。但是如果我们在NSURL变量后面去掉问号会怎么样?

import Foundation

var cheatURL: NSURL = NSURL(string: "http://www.reactive.io")

我们将会得到一个编译错误”Value of optional type ‘NSURL?’not unwrapped; did you mean to use ‘!’or ‘?’”。我们已经知道'?'是干嘛的了,那么感叹号标记'!'是干嘛的呢?

隐式和显示Optional

在一个类型后面使用'?'用来显示表明这是一个Optional,让我们使用'!'看看会发生什么:

import Foundation

var implicitURL: NSURL! = NSURL(string: "http://www.reactive.io/tips") // => http://www.reactive.io/tips

这次我们得到了一个没有被Some包装的NSURL。使用'!'使得implicitURL看起来跟rightURL这个optional差不多,用'!'标记的类型实为ImplicitlyUnwrappedOptional,当你使用值的时候Swift编译器将会自动为你展开里面的值。使用ImplicitlyUnwrappedOptional类型会带来危险,因为编译器不会迫使我们处理值为nil的情况。但是初始化为nil会帮我们跟遗留的Objective-C代码之间搭起桥梁。

下面是4中不同的Optional使用方式:

import Foundation

var rightURL: Optional<NSURL> = NSURL(string: "http://www.reactive.io") // => {Some http://www.reactive.io}
var wrongURL: NSURL? = NSURL(string: "this is not a real url") // => nil var implicitURL: NSURL! = NSURL(string: "http://www.reactive.io") // => http://www.reactive.io/tips
var explicitURL: ImplicitlyUnwrappedOptional<NSURL> = NSURL(string: "this is another bad url") // => nil

如何使用Optional

第一种方法是显示检查Optional是否是nil:

if rightURL != nil {
println("got a URL: \(rightURL)") // => "got a URL: Optional(http://www.reactive.io)"
}
else {
println("no URL, sorry :(")
}

在上面得到了Optional(http://www.reactive.io),这并不是我们想要的,我们想得到的是Optional里面包含的内容。如何做到呢?靠'!'符号,在变量或常量后面追加'!'符号将会展开Optional里面的值,如果是nil值将会抛出异常,让我们试试:

if rightURL != nil {
println("got a URL: \(rightURL!)") // => "got a URL: http://www.reactive.io"
}
else {
println("no URL, sorry :(")
}

我们可以通过另一种方式if let块来实现:

if let url = rightURL {
println("got a URL: \(url)") // => "got a URL: http://www.reactive.io"
}
else {
println("no URL, sorry :(")
}

你也可以通过switch语句来实现:

switch rightURL {
case nil:
println("no URL, sorry :(")
default:
println("got a URL: \(rightURL!)")
}

你还可以使用??操作符进行链式调用得到不为nil的值:

var myURL = wrongURL ?? explicitURL ?? rightURL // {Some http://www.reactive.io}
println("got a URL: \(myURL!)") // => "got a URL: http://www.reactive.io"

编写一个类

是时候来看看如何在面向对象的代码中使用Optional类型了,复制下面的代码到playground中:

import Foundation

class Person {
var name: String
var address: String init(name: String, address: String) {
self.name = name
self.address = address
} func description() -> String {
return "\(name) @ \(address)"
}
} class Box {
var contents: String
var sender: Person!
var recipient: Person? init(contents: String) {
self.contents = contents
}
} var alice = Person(name: "Alice", address: "New York City")
var book = Box(contents: "A Good Book") book.sender = alice // => {name "Alice" address "New York City"}

注意在Box类中,sender属性是一个ImplicitlyUnwrappedOptional类型,recipient属性是Optional类型。不过若是将这两个类型换为普通的Person类型,Swift编译器将会报出一个错误。因为这两个属性并没有在构造函数中赋值,所以这两个属性在构造函数调用的时候没有被初始化。在上面的例子中,book被初始化后,sender和recipient都默认为nil,但是我们确定book一定有一个sender,所以sender为ImplicitlyUnwrappedOptional类型,在例子中,sender为alice.但是不一定有recipient,所以别人在使用book对象的时候需要检查recipient是否有值。

方法调用

如果我们想要得到sender或者recipient的description,我们也许会得到一些麻烦。这是因为我们不能在nil值上调用description方法,另外使用!强制展开nil值Optional还会抛出异常。使用if else条件表达式调用方法会显得很繁琐。Swift提供了另一个工具,通过使用?操作符来进行链式调用。在调用方法的时候先检查值是否是nil:

book.sender?.description() // => {Some "Alice @ New York City"}
book.recipient?.description() // => nil

我们使用了一种真确的方法调用了description方法,更进一步我们得到了一个Optional类型:

book.recipient = Person(name: "Bob", address: "San Francisco")

if let note = book.recipient?.description() {
println("Hey \(note), enjoy the Book!") // => "Hey Bob @ San Francisco, enjoy the Book!"
}

总结

本文说了一些关于Swift中Optionals的事情,这将帮助你在写代码的时候更好的用上它,并且在使用类库的时候使用它们。熟练的使用不同方式的Optional将会使你保证你写代码更迅速,减少运行时的错误。