Swift - 可選鏈



可選鏈是指對可能為 nil 的可選值查詢、呼叫屬性、下標和方法的過程。可選鏈返回兩個值:

  • 如果可選值包含一個值,則呼叫其相關的屬性、方法和下標將返回相應的值。

  • 如果可選值包含 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 處理程式塊在控制檯中列印。

為可選鏈定義模型類和訪問屬性

Swift 4 語言還提供可選鏈的概念,可以宣告多個子類作為模型類。這個概念對於定義複雜的模型和訪問屬性、方法和下標的子屬性非常有用。

示例

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 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("Area of rectangle is \(rectarea)")
}  else {
   print("Rectangle Area is not specified")
}

輸出

當我們在 playground 中執行上述程式時,我們將得到以下結果:

Rectangle Area is not specified

透過可選鏈呼叫方法

示例

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("Area of Circle 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 circname.print?.circleprint() != nil {
   print("Area of circle is specified)")
} else {
   print("Area of circle is not specified")
}

輸出

當我們在 playground 中執行上述程式時,我們將得到以下結果:

Area of circle is not specified

透過建立名為“circname”的例項來呼叫在 circle() 子類中宣告的 circleprint() 函式。如果函式包含某些值,則該函式將返回值;否則,它將透過檢查語句“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("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

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。

連結多級鏈

多個子類也可以透過可選鏈與其超類的方法、屬性和下標連結。

可以連結可選值的多個鏈

示例

如果檢索型別不是可選的,則可選鏈將返回一個可選值。例如,如果透過可選鏈檢索 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 部分,而要返回值,則必須為特定成員函式定義值。

如果檢索型別已經是可選的,則可選鏈也將返回一個可選值。例如,如果透過可選鏈訪問 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("Area of Circle 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 circname.print?.circleprint() != nil {
   print("Area of circle is specified)")
}  else {
   print("Area of circle is not specified")
}

輸出

當我們在 playground 中執行上述程式時,我們將得到以下結果:

Area of circle is not specified
廣告