Swift - Optional Chaining



The process of querying, calling properties, subscripts and methods on an optional that may be 'nil' is defined as optional chaining. Optional chaining return two values −

  • if the optional contains a 'value' then calling its related property, methods and subscripts returns values

  • if the optional contains a 'nil' value all its its related property, methods and subscripts returns nil

Since multiple queries to methods, properties and subscripts are grouped together failure to one chain will affect the entire chain and results in 'nil' value.

Optional Chaining as an Alternative to Forced Unwrapping

Optional chaining is specified after the optional value with '?' to call a property, method or subscript when the optional value returns some values.

Optional Chaining '?' Access to methods,properties and subscriptsOptional Chaining '!' to force Unwrapping.
? is placed after the optional value to call property, method or subscript. ! is placed after the optional value to call property, method or subscript to force unwrapping of value.
Fails gracefully when the optional is 'nil'. Forced unwrapping triggers a run time error when the optional is 'nil'.

Program for Optional Chaining with '!'

Example

class ElectionPoll {
   var candidate: Pollbooth?
}
class Pollbooth {   
   var name = "MP"
}
  
let cand = ElectionPoll()  
let candname = cand.candidate!.name
Output

When we run the above program using playground, we get the following result −

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)

The above program declares 'election poll' as class name and contains 'candidate' as membership function. The subclass is declared as 'poll booth' and 'name' as its membership function which is initialized as 'MP'. The call to the super class is initialized by creating an instance 'cand' with optional '!'. Since the values are not declared in its base class, 'nil' value is stored thereby returning a fatal error by the force unwrapping procedure.

Program for Optional Chaining with '?'

Example

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")
}
Output

When we run the above program using playground, we get the following result −

Candidate name cannot be retreived

The program above declares 'election poll' as class name and contains 'candidate' as membership function. The subclass is declared as 'poll booth' and 'name' as its membership function which is initialized as 'MP'. The call to the super class is initialized by creating an instance 'cand' with optional '?'. Since the values are not declared in its base class 'nil' value is stored and printed in the console by the else handler block.

Defining Model Classes for Optional Chaining & Accessing Properties

Swift 4 language also provides the concept of optional chaining, to declare more than one subclasses as model classes. This concept will be very useful to define complex models and to access the properties, methods and subscripts sub properties.

Example

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")
}

Output

When we run the above program using playground, we get the following result −

Rectangle Area is not specified

Calling Methods Through Optional Chaining

Example

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")
}

Output

When we run the above program using playground, we get the following result −

Area of circle is not specified

The function circleprint() declared inside the circle() sub class is called by creating an instance named 'circname'. The function will return a value if it contains some value otherwise it will return some user defined print message by checking the statement 'if circname.print?.circleprint() != nil'.

Accessing Subscripts through Optional Chaining

Optional chaining is used to set and retrieve a subscript value to validate whether call to that subscript returns a value. '?' is placed before the subscript braces to access the optional value on the particular subscript.

Example 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.")
}

Output

When we run the above program using playground, we get the following result −

Radius is not specified.

In the above program the instance values for the membership function 'radiusName' is not specified. Hence program call to the function will return only else part whereas to return the values we have to define the values for the particular membership function.

Example 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.")
}

Output

When we run the above program using playground, we get the following result −

Radius is measured in Units.

In the above program, the instance values for the membership function 'radiusName' is specified. Hence program call to the function will now return values.

Accessing Subscripts of Optional Type

Example

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])

Output

When we run the above program using playground, we get the following result −

Optional(35)
Optional(78)
Optional(78)
Optional(101)
Optional(90)
Optional(44)
Optional(56)

The optional values for subscripts can be accessed by referring their subscript values. It can be accessed as subscript[0], subscript[1] etc. The default subscript values for 'radius' are first assigned as [35, 45, 78, 101] and for 'Circle' [90, 45, 56]]. Then the subscript values are changed as Radius[0] to 78 and Circle[1] to 45.

Linking Multiple Levels of Chaining

Multiple sub classes can also be linked with its super class methods, properties and subscripts by optional chaining.

Multiple chaining of optional can be linked:

Example

If retrieving type is not optional, optional chaining will return an optional value. For example if String through optional chaining it will return String? Value.

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.")
}

Output

When we run the above program using playground, we get the following result −

Radius is not specified.

In the above program, the instance values for the membership function 'radiusName' is not specified. Hence, the program call to the function will return only else part whereas to return the values we have to define the values for the particular membership function.

If the retrieving type is already optional, then optional chaining will also return an optional value. For example if String? Is accessed through optional chaining it will return String? Value.

Example

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.")
}

Output

When we run the above program using playground, we get the following result −

Radius is measured in Units.

In the above program, the instance values for the membership function 'radiusName' is specified. Hence, the program call to the function will now return values.

Chaining on Methods with Optional Return Values

Optional chaining is used to access subclasses defined methods too.

Example

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")
}

Output

When we run the above program using playground, we get the following result: −

Area of circle is not specified
Advertisements