原文:Optional Chaining in Swift – AndyBargh.com

上一篇文章中,咱们了解了 Swift 可选类型的基本常识,它是什么以及怎么运用。在这篇文章中,我想在这一核心常识的基础上,研讨可选链。

什么是可选链?

可选链是一个咱们可以与可选类型结合运用的进程,以调用可选类型或许为 nil 也或许不为 nil 的特点、办法和下标。它本质上答应咱们根据可选类型是否包括值来挑选性地履行不同的代码片段。这有助于使咱们的代码更清晰,并答应咱们抛弃在上一篇文章中看到的一些语法。让咱们看一个比方:

class DriversLicence {
    var pointsOnLicence = 0
}
class Person {
    var licence : DriversLicence?
}

在这个比方中,咱们界说了两个类:一个是 DriversLicence 类,它有一个名为 pointOnLicence 的特点(显然它总是被设置为 0!),另一个是 Person 类。Person 类包括人的姓名、姓氏和一个引证人的驾驭执照的(可选类型)licence 特点。

我将 licence 特点声明为可选类型,由于有些人有驾照,而有些人没有。我还界说了 Person 类有一个可失利结构器,由于我不期望这个类在姓名或姓氏特点被提供为空字符串时被初始化。

现在,这些类已经被界说了,让咱们来创立一个 Person 实例:

let andy = Person()

现在,假定我想打印出我的驾照上的积分数。为了打印出积分,我有必要遍历可选的 driversLicence 特点,以获得相关的 DriversLicence 实例。但问题是 licence 特点是可选类型,在企图拜访该特点之前,我一般要查看它是否为零。一种办法是运用可选绑定语法:

if let licence = andy.licence {
    print("Andy has (licence.pointsOnLicence) points on his license")
} else {
    print("Andy doesn't have a drivers licenses")
}
// prints "Andy doesn't have a licence."

另一个挑选是运用可选链(optional chaining)

特点中的可选链

经过可选链读取特点值

正如我方才说到的,可选链答应咱们拜访一个可选类型的变量或常量特点,而不需求提早查看该可选类型的变量是否包括一个 nil 值。

经过可选链,假如可选类型包括值,那么链式句子中剩下的代码就会被履行,假如是 nil,那么链就会断开,整个链式句子会回来 nil。下面是一个比方:

let pointsOnLicence = andy.licence?.pointsOnLicence
if let points = pointsOnLicence {
    print("Andy has (points) points on his licence")
} else {
    print("Andy doesn't have a drivers licence")
}
// prints "Andy doesn't have a drivers licence."

在这个比方中,有几件事需求留意:

榜首件事,是比方中榜首行代码的可选类型特点结尾的 ?

这个问号与咱们在运用可选类型时一般看到的问号的含义略有不同。在这种情况下,咱们把它用在一个可选类型的变量、常量或特点的结尾(而不是在一个类型保留字的结尾)。在这儿,它的意思是 “假如该可选类型有一个值,就拜访这个变量的特点,不然回来 nil”。这与咱们之前运用的强制解包操作符(!)的含义相似,但在这儿,假如该可选类型包括一个 nil 值,那么链式句子就会高雅地失利,而不是让你的应用程序运行崩溃。

在咱们比方中的 licence 特点的情况下,这意味着假如它包括一个值,那么链式句子将持续,咱们将拜访其 pointOnLicense 特点,不然,链式句子将中止,并回来 nil

在这个比方中,需求留意的另一件事是,**经过可选链拜访一个特点的成果,其自身总是一个可选类型。**假如你细心想想,这便是相对的逻辑。

在链中的任何一点,其间一个可选类型或许包括也或许不包括 nil 值。假如是这样,链式句子就断了,此刻,链式句子的成果将等同于链式句子开裂前的最终一环的值(也便是值为 nil 的那个可选类型)。

假如链式句子成功履行,它反而会回来一个与链式句子中最终一个特点的类型相匹配的值。

因而,假如你退后一步,链式句子的成果或许是 nil 或许一个特定类型的值。听起来很熟悉吧?是的,你猜对了,一个可选类型。在上面的比方中,这意味着链式句子的成果实际上是一个 Int? 而不是一个 Int,由于 licence 或许会回来 nil

在这个比方中,为了打印出成果,我已经明确地将可选链的成果与解包后的可选类型分开。正如你在本文后边所看到的,你可以把它们合并成一行。不过在这儿我想强调的是,可选链是独立于可选绑定的。这是一个需求留意的重要事项。

经过可选链设置特点值

现在,除了可以经过可选链检索特点的值之外,咱们还可以运用可选链来设置它们。与经过可选赋值拜访特点的办法相似,赋值只发生在链中的可选元素都包括非 nil 值的情况下:

var points = 3
andy.licence?.pointsOnLicence = points

在这种情况下,赋值实际上是失利的,由于 licence 特点现在包括一个 nil 值。这一切都很好,但问题是,咱们怎么知道赋值是否成功?

一般情况下,Swift 中的赋值运算符没有回来类型(以防止它被意外地用来替代 == 运算符),但假如你在 playground 里跟着走,你会看到一些古怪的东西。

当与可选链结合运用时,赋值运算符实际上回来一个 Void 类型的值?(也可以写成 ()? —— 一个空的、可选的元组类型),假如赋值失利则回来 nil,假如赋值成功则回来一个空元组(())。

因而咱们可以重写上面的比方,运用这一实际来查看咱们的赋值是否成功:

if let result = andy.licence?.pointsOnLicence += points {
    print("Andy now has (andy.licence!.pointsOnLicence) points on his licence.")
}
// Prints 'Andy now has 3 points on his licence.'

让咱们来看看它。在赋值运算符的右侧,咱们有咱们的可选链。这儿咱们用来为 pointOnLicence 特点设置一个值。正如咱们在上面看到的,表达式的成果被揣度为可选的,所以咱们在这儿将它与可选的绑定结合起来,查看成果是否为 nil。假如赋值成功,result 常量被创立,从表达式回来的值被赋值(在本例中是一个空元组(()),if 句子分支被履行。

类型的下标语法回来可选类型

现在咱们已经清楚了可选类型特点链的基础常识,让咱们看看一个稍微复杂的比方。正如你或许知道的,Swift 中的一些数据类型支持经过下标来拜访值。Swift 中的 ArrayDictionary 都是比方。但有时,这些下标会回来一个可选类型。让咱们看一个比方。

让咱们首要创立一个 Dictionary,将轿车模型(例如本田思域)与或许期望出售的最低和最高价格相匹配。咱们将把最低和最高价格存储为一个元组。

var catalogue = ["Honda" : (minPrice:10, maxPrice:100)]

当咱们运用下标语法拜访 Dictionary 中的值时,从下标回来的值是一个可选类型。这是由于 key,也便是咱们放在下标括号中的值,实际上或许不存在于字典中。

var honda = catalogue["Honda"]

在这种情况下,变量 honda 被揣度为 (minPrice:Int, maxPrice:Int)? 类型,一个包括两个值的可选元组类型;

假如你挑选点击 playground 中的 honda 变量,你可以自己看到这一点。

留意:这对本文并不要害,但假如你对在 Tuples 中运用命名的值有点朦胧,请去看看我关于 Tuples 的文章,完成后再来这儿。别忧虑,我会等的。

还在这儿吗?对了,咱们已经有了咱们的可选元组类型,但是假定咱们想在可选链中运用这个元组,然后打印出回来的可选类型的最小价格部分:

if let price = catalogue["Honda"]?.minPrice {
    print("最低价格是 (price).")
}
// 最低价格是 10.

尽管它看起来有点古怪,但它与咱们之前看到的可选链没有什么不同。在这种情况下,咱们拜访 catalogue 字典,问询与 key Honda Civic 相关的值。正如咱们方才所看到的,这将回来一个可选的元组。正如咱们之前所看到的,咱们在可选的后边加一个问号(?),然后用链的其余部分来拜访元组中的 minPrice 值。和之前的其他链子相同,回来的值自身便是一个可选项,咱们用可选绑定办法将其解包并打印成果。

咱们也可以运用相同的语法来设置值。这儿咱们再次依托这样一个实际:当经过可选链设置一个特点时,会回来一个可选元组(Void?()?)。

if let result = catalogue["Honda"]?.maxPrice = 30 {
    print(catalogue)
}

好吧,相对直接。但让咱们看看另一个比方。

经过下标拜访可选类型回来的可选值

这一次,假如 catalogue 自身是一个可选的字典呢?

var otherCatalogue : Dictionary? = ["Lotus" : (minPrice: 50, maxPrice: 200)]

让咱们慢慢来。首要让咱们研讨一下怎么拜访字典的内容:

var lotus = otherCatalogue?["Lotus"]

正如你所看到的,由于字典自身是可选类型,咱们需求在字典的称号后边运用问号,以便拜访它的内容。问号被放在姓名和左方括号之间。在实际中,这实际上是一个可选的链,它基本上是在说 “假如 otherCatalogue 不是 nil (也便是字典自身),就用键 Lotus 拜访下标”。

和前面的比方相同,链式语法回来的值被揣度为 (minPrice:Int, maxPrice:Int)? 类型。

好的,到现在为止还不错。现在,让咱们把它与拜访元组中的 minPrice 值结合起来。咱们在前面的比方中看到怎么做到这一点。咱们将再次运用可选绑定来打印它:

if let price = otherCatalogue?["Lotus"]?.minPrice {
    print("The minimum price is (price)")
}
// The minimum price is 50

留意到这个额定的问号了吗?这一次,咱们在字典的下标后边和前面都加了一个问号。这第二个问号实际上是在说。”拜访下标 ‘Lotus’ 中的值,假如回来的值不是 nil,则拜访回来元组中的 minPrice 值。

办法的可选链

所以,咱们已经看了怎么运用可选链来经过可选值获取和设置特点,但相同的概念也可以用来调用办法。让咱们重新审视一下咱们的比方。

这一次,我扩展了这个比方,加入了车辆的概念,它或许有也或许没有车主。我还修改了 Person 类,使其包括一个数组来保存个人拥有的车辆,最终 DriversLicence 类获得了一个新的函数,用来回来 licence是否对某一特定车辆有效(现在它的默许完成总是回来 true)。

class DriversLicence {
    var pointsOnLicence = 0
    func isValidForVehicle(vehicle: Vehicle) -> Bool {
        // ...
        return true
    }
}
class Vehicle {
    var owner: Person?
}
class Person {
    var licence : DriversLicence?
}

现在,正如我说到的,咱们也可以运用可选链来有条件地调用一个可选类型的办法。在下面的比方中,咱们运用可选链来确认我的驾驭执照是否答应我驾驭轿车:

let andy = Person()
andy.licence = DriversLicence()
let car = Vehicle()
car.owner = andy
if let canDriveVehicle = andy.licence?.isValidForVehicle(vehicle: car) {
    if canDriveVehicle {
        print("Andy's licence allows him to drive the car.")
    } else {
        print("Andy's license doesn't allow him to drive the car.")
    }
} else {
    print("Andy doesn't have a licence.")
}
// Andy's licence allows him to drive the car.

那么这儿发生了什么?与经过可选链拜访特点的办法相似,链中可选项右边的代码(本例中是对isValidForVehicle()的调用)只要在 licence 可选项中的值不是 nil 时才会被履行。假如license的确包括一个值,那么该链会回来调用isValidForVehicle()办法得到的值(一个Bool),不然会回来nil。该链的成果是一个可选的Bool类型的值。

正如咱们屡次看到的那样,为了拜访一个可选类型值,咱们需求把它解包。在这个比方中,我再次将可选链和可选绑定结合起来,以拜访成果中的可选类型内容。因而canDriveVehicle常量只要在链的成果不是nil时才会被界说。假如它的确包括一个值,那么if句子的榜首个分支就会被履行,咱们评估该常量所包括的值(要么是真,要么是假),并打印出我是否能驾驭轿车。

在这个比方中,由于我把 isValidForVehicle() 办法默许完成总是回来 true,看起来我很走运,我可以驾驭这辆车了。

链的多个层级

除了运用可选链来遍历包括单一可选的链之外,咱们还可以将这个主意扩展到包括多个可选的链上。这适用于拜访特点、调用函数,乃至拜访一个类型上的下标(比方拜访一个可选数组的内容)。

例如,假定我拿着上面的轿车实例,想知道车主的驾照上有多少分:

if let points = car.owner?.licence?.pointsOnLicence {
    print("The car's owner has (points) points on their licence.")
}
// The car's owner has 0 points on their licence.

正如你在这儿看到的,现在的可选链包括了多个可选类型,而不是只要一个。尽管这样,一切的规矩都适用。

可选链自身会回来一个可选类型。假如链中的任何一个可选类型为 nil,该链将回来 nil,不然将回来 pointOnLicence 特点的值。在这种情况下,回来类型仍然是 Int? (而不是 Int??? 或其他变体),由于工作不会由于咱们在链中遍历了一个以上的可选类型而变得更加可选。

咱们也可以用链中的多个可选类型调用办法。例如,让咱们查看车主是否有答应他们驾驭轿车的 licence

if let canDriveVehicle = car.owner?.licence?.isValidForVehicle(vehicle: car) {
    if canDriveVehicle {
        print("The owner of the car has a licence that allows them to drive it.")
    } else {
        print("The owner of the car doesn't have a licence that allows them to drive it.")
    }
} else {
    print("The car either doesn't have an owner or the owner doesn't have a drivers licence.")
}
// The owner of the car has a licence that allows them to drive it.

可选协议中的可选链

好了,咱们快到了,但在咱们结束之前,我还想看一件事。到现在为止,在咱们所看的一切比方中,我都是用可选类型来处理那些或许不回来值的东西。但除此之外,可选链还可以用来拜访或许并不存在的特点和办法,例如可选协议要求(optional protocol requirements)

当界说一个协议时,一些办法或特点可以被标记为可选。现在,在 Swift 中,咱们经过在协议声明前加上 @objc,然后在特点和办法声明前运用 optional 要害字来完成这一点:

@objc protocol MyProtocol {
    @objc optional var x: Int { get set }
    @objc optional func optionalMethod()
}

现在,让咱们声明两个恪守这个协议的类。榜首个类将一起完成可选的特点和可选的办法,而第二个类则没有。在榜首个类中,咱们还有必要用 @objc 要害字来注解特点和办法,以保证它们满意 @objc 协议的要求:

class classA: MyProtocol {
    @objc var x: Int = 0
    @objc func optionalMethod() {
        print("Optional Method Called")
    }
}
class classB: MyProtocol {}

接下来,让咱们声明这些类的两个实例。在这两种情况下,咱们将经过它们的协议来引证它们,而不是直接引证这些类。这将使咱们可以测试出特点和办法的可选性质:

var a: MyProtocol = classA()
var b: MyProtocol = classB()

好了,咱们都预备好了,让咱们先看看怎么拜访 x 特点。 在协议中拜访一个被标记为可选的特点是很简单的,它就像拜访一个没有被标记为可选的特点相同,仅仅回来的值的类型总是一个可选的:

let aValue = a.x
if let aValue = aValue {
    print(aValue)
}
// prints 0
let bValue = b.x
if let bValue = bValue {
    print(bValue)
}
// Doesn't print anything.

这儿,aValuebValue 都被揣度为 Int? 类型。但只要 a 的成果是打印出来的。这是由于只要 classA 真实完成了 x 特点。在 classB 实例中,咱们企图拜访该特点,但失利了,而且回来了 nil。这是一个可选链高雅失利的比方。

那么办法呢?让咱们看一下:

a.optionalMethod?()
// Prints "Optional Method Called."
b.optionalMethod?()
// Doesn't print anything.

正如你所看到的,在调用协议中被标记为 optional 的办法时,咱们又有必要运用问号。这一次,咱们在办法的称号和圆括号之间运用问号。相同,这是一个小型的可选链。只要当办法存在时,该办法的调用(括号)才会被履行。这与咱们之前看到的将问号放在括号之后有纤细的不同,问号意味着咱们要查看从办法回来的值。

好了,今天就到这儿了。我期望这有协助。像以往相同,假如你有问题、谈论或更正,请联系咱们。直到下一次。