Swift - 可选链
对可能为 nil 的 optional 进行查询、调用属性、下标和方法的过程被称为 optional chaining。可选链会返回两个值 —
如果 optional 包含一个“值”,则调用其相关属性、方法和下标会返回相应值
如果 optional 包含
nil值,则其所有相关属性、方法和下标都会返回nil
由于对方法、属性和下标的多个查询被组合在一起,如果链中的任何一个环节失败,整个链都会受到影响,并返回 nil 值。
可选链式调用作为强制解包的替代方案
可选链式调用在可选值后使用 ? 来调用属性、方法或下标,当可选值包含某些值时。
可选链式调用 ? |
访问方法、属性和下标;可选链式调用 ! 用于强制解包。 |
? 放置在可选值后,用于调用属性、方法或下标。 |
! 放置在可选值后,用于调用属性、方法或下标以强制解包值。 |
当可选值为 nil 时,会优雅地失败。 |
当可选值为 nil 时,强制解包会触发运行时错误。 |
使用 '!' 的可选链式调用程序
示例
class ElectionPoll {
var candidate: Pollbooth?
}
class Pollbooth {
var name = "MP"
}
let cand = ElectionPoll()
let candname = cand.candidate!.name
输出
在 playground 中运行上述程序时,我们会得到以下结果 −
main/main.swift:10: Fatal error: Unexpectedly found nil while unwrapping an Optional value Current stack trace: 0 libswiftCore.so 0x00007fd880a40dc0 _swift_stdlib_reportFatalErrorInFile + 112 1 libswiftCore.so 0x00007fd88070a191 <unavailable> + 1442193 2 libswiftCore.so 0x00007fd880709eb6 <unavailable> + 1441462 3 libswiftCore.so 0x00007fd880709caa <unavailable> + 1440938 4 libswiftCore.so 0x00007fd8807096d0 _assertionFailure(_:_:file:line:flags:) + 315 6 swift-frontend 0x000055a564ac0b3d <unavailable> + 26479421 7 swift-frontend 0x000055a563df4db9 <unavailable> + 13061561 8 swift-frontend 0x000055a563bc54c6 <unavailable> + 10769606 9 swift-frontend 0x000055a563bc19b6 <unavailable> + 10754486 10 swift-frontend 0x000055a563bc10a7 <unavailable> + 10752167 11 swift-frontend 0x000055a563bc341e <unavailable> + 10761246 12 swift-frontend 0x000055a563bc273d <unavailable> + 10757949 13 swift-frontend 0x000055a563a94a39 <unavailable> + 9521721 14 libc.so.6 0x00007fd880017d90 <unavailable> + 171408 15 libc.so.6 0x00007fd880017dc0 __libc_start_main + 128 16 swift-frontend 0x000055a563a94295 <unavailable> + 9519765 Stack dump: 0. Program arguments: /opt/swift/bin/swift-frontend -frontend -interpret main.swift -disable-objc-interop -color-diagnostics -new-driver-path /opt/swift/bin/swift-driver -empty-abi-descriptor -resource-dir /opt/swift/lib/swift -module-name main 1. Swift version 5.7.3 (swift-5.7.3-RELEASE) 2. Compiling with the current language version 3. While running user code "main.swift" Stack dump without symbol names (ensure you have llvm-symbolizer in your PATH or set the environment var `LLVM_SYMBOLIZER_PATH` to point to it): /opt/swift/bin/swift-frontend(+0x551a103)[0x55a56869a103] /opt/swift/bin/swift-frontend(+0x551802e)[0x55a56869802e] /opt/swift/bin/swift-frontend(+0x551a48a)[0x55a56869a48a] /lib/x86_64-linux-gnu/libc.so.6(+0x42520)[0x7fd880030520] /opt/swift/lib/swift/linux/libswiftCore.so(+0x160195)[0x7fd88070a195] /opt/swift/lib/swift/linux/libswiftCore.so(+0x15feb6)[0x7fd880709eb6] /opt/swift/lib/swift/linux/libswiftCore.so(+0x15fcaa)[0x7fd880709caa] /opt/swift/lib/swift/linux/libswiftCore.so($ss17_assertionFailure__4file4line5flagss5NeverOs12StaticStringV_A2HSus6UInt32VtF+0x13b)[0x7fd88070980b] [0x7fd87ece717e] /opt/swift/bin/swift-frontend(+0x1940b3d)[0x55a564ac0b3d] /opt/swift/bin/swift-frontend(+0xc74db9)[0x55a563df4db9] /opt/swift/bin/swift-frontend(+0xa454c6)[0x55a563bc54c6] /opt/swift/bin/swift-frontend(+0xa419b6)[0x55a563bc19b6] /opt/swift/bin/swift-frontend(+0xa410a7)[0x55a563bc10a7] /opt/swift/bin/swift-frontend(+0xa4341e)[0x55a563bc341e] /opt/swift/bin/swift-frontend(+0xa4273d)[0x55a563bc273d] /opt/swift/bin/swift-frontend(+0x914a39)[0x55a563a94a39] /lib/x86_64-linux-gnu/libc.so.6(+0x29d90)[0x7fd880017d90] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0x80)[0x7fd880017e40] /opt/swift/bin/swift-frontend(+0x914295)[0x55a563a94295] Illegal instruction (core dumped)
上述程序将 election poll 声明为类名,并包含 candidate 作为成员属性。子类声明为 poll booth,其成员属性 name 初始化为 MP。通过创建实例 cand 并使用可选 ! 来调用父类。由于基类中未声明值,因此存储了 nil 值,从而通过强制解包过程返回致命错误。
使用 ? 的可选链式调用程序
示例
class ElectionPoll {
var candidate: Pollbooth?
}
class Pollbooth {
var name = "MP"
}
let cand = ElectionPoll()
if let candname = cand.candidate?.name {
print("Candidate name is \(candname)")
} else {
print("Candidate name cannot be retreived")
}
输出
在 playground 中运行上述程序时,我们会得到以下结果 −
Candidate name cannot be retreived
上述程序将 election poll 声明为类名,并包含 candidate 作为成员属性。子类声明为 poll booth,其成员属性 name 初始化为 MP。通过创建实例 cand 并使用可选 ? 来调用父类。由于基类中未声明值,因此存储了 nil 值,并通过 else 处理块在控制台中打印。
为可选链和访问属性定义 Model Classes
Swift 4 语言还提供了可选链的概念,用于声明多个子类作为 model classes。这个概念对于定义复杂模型以及访问属性、方法和下标子属性非常有用。
示例
class rectangle {
var print: circle?
}
class circle {
var area = [radius]()
var cprint: Int {
return area.count
}
subscript(i: Int) -> radius {
get {
return area[i]
}
set {
area[i] = newValue
}
}
func circleprint() {
print("房间数量是 \(cprint)")
}
var rectarea: circumference?
}
class radius {
let radiusname: String
init(radiusname: String) { self.radiusname = radiusname }
}
class circumference {
var circumName: String?
var circumNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if circumName != nil {
return circumName
} else if circumNumber != nil {
return circumNumber
} else {
return nil
}
}
}
let rectname = rectangle()
if let rectarea = rectname.print?.cprint {
print("矩形面积是 \(rectarea)")
} else {
print("矩形面积未指定")
}
输出
当我们在 playground 中运行上述程序时,会得到以下结果 −
矩形面积未指定
通过可选链调用方法
示例
class rectangle {
var print: circle?
}
class circle {
var area = [radius]()
var cprint: Int {
return area.count
}
subscript(i: Int) -> radius {
get {
return area[i]
}
set {
area[i] = newValue
}
}
func circleprint() {
print("圆的面积是: \(cprint)")
}
var rectarea: circumference?
}
class radius {
let radiusname: String
init(radiusname: String) { self.radiusname = radiusname }
}
class circumference {
var circumName: String?
var circumNumber: String?
var circumarea: String?
func buildingIdentifier() -> String? {
if circumName != nil {
return circumName
} else if circumNumber != nil {
return circumNumber
} else {
return nil
}
}
}
let circname = rectangle()
if circname.print?.circleprint() != nil {
print("圆的面积已指定)")
} else {
print("圆的面积未指定")
}
输出
当我们在 playground 中运行上述程序时,会得到以下结果 −
圆的面积未指定
circle 子类中声明的函数 circleprint() 通过创建名为 'circname' 的实例来调用。如果该函数包含某些值,它将返回一个值;否则,通过检查语句 'if circname.print?.circleprint() != nil' 来返回一些用户定义的打印消息。
通过可选链访问下标
可选链用于设置和获取下标值,以验证对该下标的调用是否返回一个值。在下标括号前放置“?”,以访问特定下标上的可选值。
示例 1
class rectangle {
var print: circle?
}
class circle {
var area = [radius]()
var cprint: Int {
return area.count
}
subscript(i: Int) -> radius {
get {
return area[i]
}
set {
area[i] = newValue
}
}
func circleprint() {
print("The number of rooms is \(cprint)")
}
var rectarea: circumference?
}
class radius {
let radiusname: String
init(radiusname: String) { self.radiusname = radiusname }
}
class circumference {
var circumName: String?
var circumNumber: String?
var circumarea: String?
func buildingIdentifier() -> String? {
if circumName != nil {
return circumName
} else if circumNumber != nil {
return circumNumber
} else {
return nil
}
}
}
let circname = rectangle()
if let radiusName = circname.print?[0].radiusname {
print("The first room name is \(radiusName).")
} else {
print("Radius is not specified.")
}
输出
当我们在 playground 中运行上述程序时,会得到以下结果 −
Radius is not specified.
在上述程序中,成员函数 'radiusName' 的实例值未指定。因此,程序调用该函数时只会返回 else 分支;要返回值,必须为特定成员函数定义值。
示例 2
class rectangle {
var print: circle?
}
class circle {
var area = [radius]()
var cprint: Int {
return area.count
}
subscript(i: Int) -> radius {
get {
return area[i]
}
set {
area[i] = newValue
}
}
func circleprint() {
print("The number of rooms is \(cprint)")
}
var rectarea: circumference?
}
class radius {
let radiusname: String
init(radiusname: String) { self.radiusname = radiusname }
}
class circumference {
var circumName: String?
var circumNumber: String?
var circumarea: String?
func buildingIdentifier() -> String? {
if circumName != nil {
return circumName
} else if circumNumber != nil {
return circumNumber
} else {
return nil
}
}
}
let circname = rectangle()
circname.print?[0] = radius(radiusname: "Diameter")
let printing = circle()
printing.area.append(radius(radiusname: "Units"))
printing.area.append(radius(radiusname: "Meter"))
circname.print = printing
if let radiusName = circname.print?[0].radiusname {
print("Radius is measured in \(radiusName).")
} else {
print("Radius is not specified.")
}
输出
当我们在 playground 中运行上述程序时,会得到以下结果 −
Radius is measured in Units.
在上述程序中,成员函数 'radiusName' 的实例值已指定。因此,程序调用该函数时现在会返回值。
访问可选类型的下标
示例
class rectangle {
var print: circle?
}
class circle {
var area = [radius]()
var cprint: Int {
return area.count
}
subscript(i: Int) -> radius {
get {
return area[i]
}
set {
area[i] = newValue
}
}
func circleprint() {
print("房间数量是 \(cprint)")
}
var rectarea: circumference?
}
class radius {
let radiusname: String
init(radiusname: String) { self.radiusname = radiusname }
}
class circumference {
var circumName: String?
var circumNumber: String?
var circumarea: String?
func buildingIdentifier() -> String? {
if circumName != nil {
return circumName
} else if circumNumber != nil {
return circumNumber
} else {
return nil
}
}
}
let circname = rectangle()
circname.print?[0] = radius(radiusname: "Diameter")
let printing = circle()
printing.area.append(radius(radiusname: "Units"))
printing.area.append(radius(radiusname: "Meter"))
circname.print = printing
var area = ["Radius": [35, 45, 78, 101], "Circle": [90, 45, 56]]
area["Radius"]?[1] = 78
area["Circle"]?[1]--
print(area["Radius"]?[0])
print(area["Radius"]?[1])
print(area["Radius"]?[2])
print(area["Radius"]?[3])
print(area["Circle"]?[0])
print(area["Circle"]?[1])
print(area["Circle"]?[2])
输出
当我们在 playground 中运行上述程序时,会得到以下结果 −
Optional(35) Optional(78) Optional(78) Optional(101) Optional(90) Optional(44) Optional(56)
可选类型的下标值可以通过引用其下标值来访问,例如 subscript[0]、subscript[1] 等。首先为 'radius' 分配默认下标值 [35, 45, 78, 101],为 'Circle' 分配 [90, 45, 56]。然后修改下标值,将 Radius[0] 改为 78,Circle[1] 改为 45。
链接多级链式调用
多个子类也可以通过 optional chaining 与其超类的成员方法、属性和下标进行链接。
可选类型的多级链式调用也可以进行链接:
示例
如果检索的类型不是 optional 类型,则 optional chaining 将返回一个 optional 值。例如,通过 optional chaining 访问 String 将返回 String? 值。
class rectangle {
var print: circle?
}
class circle {
var area = [radius]()
var cprint: Int {
return area.count
}
subscript(i: Int) -> radius {
get {
return area[i]
}
set {
area[i] = newValue
}
}
func circleprint() {
print("The number of rooms is \(cprint)")
}
var rectarea: circumference?
}
class radius {
let radiusname: String
init(radiusname: String) { self.radiusname = radiusname }
}
class circumference {
var circumName: String?
var circumNumber: String?
var circumarea: String?
func buildingIdentifier() -> String? {
if circumName != nil {
return circumName
} else if circumNumber != nil {
return circumNumber
} else {
return nil
}
}
}
let circname = rectangle()
if let radiusName = circname.print?[0].radiusname {
print("The first room name is \(radiusName).")
} else {
print("Radius is not specified.")
}
输出
当我们在 playground 中运行上述程序时,会得到以下结果 −
Radius is not specified.
在上述程序中,成员函数 'radiusName' 的实例值未指定。因此,程序调用该函数时只会返回 else 分支;要返回值,必须为该特定成员函数定义值。
如果检索的类型已经是 optional 类型,则 optional chaining 也将返回一个 optional 值。例如,通过 optional chaining 访问 String? 将返回 String? 值。
示例
class rectangle {
var print: circle?
}
class circle {
var area = [radius]()
var cprint: Int {
return area.count
}
subscript(i: Int) -> radius {
get {
return area[i]
}
set {
area[i] = newValue
}
}
func circleprint() {
print("The number of rooms is \(cprint)")
}
var rectarea: circumference?
}
class radius {
let radiusname: String
init(radiusname: String) { self.radiusname = radiusname }
}
class circumference {
var circumName: String?
var circumNumber: String?
var circumarea: String?
func buildingIdentifier() -> String? {
if circumName != nil {
return circumName
} else if circumNumber != nil {
return circumNumber
} else {
return nil
}
}
}
let circname = rectangle()
circname.print?[0] = radius(radiusname: "Diameter")
let printing = circle()
printing.area.append(radius(radiusname: "Units"))
printing.area.append(radius(radiusname: "Meter"))
circname.print = printing
if let radiusName = circname.print?[0].radiusname {
print("Radius is measured in \(radiusName).")
} else {
print("Radius is not specified.")
}
输出
当我们在 playground 中运行上述程序时,会得到以下结果 −
Radius is measured in Units.
在上述程序中,成员函数 'radiusName' 的实例值已指定。因此,程序调用该函数时现在会返回值。
具有可选返回值的函数链式调用
可选链式调用也可用于访问定义的子类方法。
示例
class rectangle {
var print: circle?
}
class circle {
var area = [radius]()
var cprint: Int {
return area.count
}
subscript(i: Int) -> radius {
get {
return area[i]
}
set {
area[i] = newValue
}
}
func circleprint() {
print("圆的面积是:\(cprint)")
}
var rectarea: circumference?
}
class radius {
let radiusname: String
init(radiusname: String) { self.radiusname = radiusname }
}
class circumference {
var circumName: String?
var circumNumber: String?
var circumarea: String?
func buildingIdentifier() -> String? {
if circumName != nil {
return circumName
} else if circumNumber != nil {
return circumNumber
} else {
return nil
}
}
}
let circname = rectangle()
if circname.print?.circleprint() != nil {
print("圆的面积已指定)")
} else {
print("圆的面积未指定")
}
输出
当我们在 playground 中运行上述程序时,会得到以下结果:−
圆的面积未指定