YouTip LogoYouTip

Swift Optional Chaining

Optional Chaining is a process that can request and call properties, methods, and subscripts, where the target being requested or called may be nil.\n\nOptional chaining returns two values:\n\n* If the target has a value, the call succeeds and returns that value\n\n* If the target is nil, the call returns nil\n\nMultiple requests or calls can be chained together into a chain, and if any node is nil, the entire chain will fail.\n\n* * *\n\n## Optional Chaining as an Alternative to Forced Unwrapping\n\nYou can define an optional chain by placing a question mark (?) after the optional value of a property, method, or subscript.\n\nOptional Chaining '?'Exclamation mark (!) forced unwrapping method, property, subscript optional chaining\n? placed after the optional value to call methods, properties, subscripts! placed after the optional value to call methods, subscripts to force unwrap the value\nWhen optional is nil, outputs a more friendly error messageWhen optional is nil, force unwrapping causes a runtime error\n\n### Optional Chaining Instance Using Exclamation Mark (!)\n\n
class Person {\n    var residence: Residence?\n}\n\nclass Residence {\n    var numberOfRooms = 1\n}\n\nlet john = Person()\n\n//will cause a runtime error\nlet roomCount = john.residence!.numberOfRooms
\n\nThe output of the above program execution is:\n\n
fatal error: unexpectedly found nil while unwrapping an Optional value
\n\nUsing an exclamation mark (!) to force unwrap and get the value of the numberOfRooms property of this person's residence property will trigger a runtime error because there is no residence value available for unwrapping at this time.\n\n### Optional Chaining Instance Using Question Mark (?)\n\n
class Person {\n    var residence: Residence?\n}\n\nclass Residence {\n    var numberOfRooms = 1\n}\n\nlet john = Person()\n\n// Optional chaining on residence?property, retrieves the value of numberOfRooms if residence exists\nif let roomCount = john.residence?.numberOfRooms {\n    print("John 's room number is \\(roomCount)。")\n} else {\n    print("Cannot view room number")\n}
\n\nThe output of the above program execution is:\n\n
Cannot view room number
\n\nBecause the attempt to get numberOfRooms might fail, optional chaining returns an Int? type value, or "optional Int". When residence is empty (as in the example above), the optional Int will be empty, thus resulting in the inability to access numberOfRooms.\n\nIt is important to note that this holds true even when numberOfRooms is a non-optional Int (Int?). As long as the request is made through optional chaining, it means numberOfRooms will always return an Int? instead of an Int.\n\n* * *\n\n## Defining Model Classes for Optional Chaining\n\nYou can use optional chaining to call properties, methods, and subscripts across multiple levels. This allows you to leverage complex models between them to access lower-level properties and check whether such lower-level properties can be successfully accessed.\n\n### Instance\n\nFour model classes are defined, including multi-level optional chaining:\n\n
class Person {\n    var residence: Residence?\n}\n\n// Defines a variable rooms, which is initialized as a Room[]empty array of type\nclass Residence {\n    var rooms = ()\n    var numberOfRooms: Int {\n        return rooms.count\n    }\n    subscript(i: Int) -> Room {\n        return rooms\n    }\n    func printNumberOfRooms() {\n        print("Room number is \\(numberOfRooms)")\n    }\n    var address: Address?\n}\n\n// Room Defines a name property and an initializer that sets the room name\nclass Room {\n    let name: String\n    init(name: String) { self.name = name }\n}\n\n// The final class in the model is called Address\nclass Address {\n    var buildingName: String?\n    var buildingNumber: String?\n    var street: String?\n    func buildingIdentifier() -> String? {\n        if (buildingName != nil) {\n            return buildingName\n        } else if (buildingNumber != nil) {\n            return buildingNumber\n        } else {\n            return nil\n        }\n    }\n}
\n\n* * *\n\n## Calling Methods Through Optional Chaining\n\nYou can use optional chaining to call methods on optional values and check whether the method call is successful. Even if the method does not have a return value, you can still use optional chaining to achieve this purpose.\n\n
class Person {\n    var residence: Residence?\n}\n\n// Defines a variable rooms, which is initialized as a Room[]empty array of type\nclass Residence {\n    var rooms = ()\n    var numberOfRooms: Int {\n        return rooms.count\n    }\n    subscript(i: Int) -> Room {\n        return rooms\n    }\n    func printNumberOfRooms() {\n        print("Room number is \\(numberOfRooms)")\n    }\n    var address: Address?\n}\n\n// Room Defines a name property and an initializer that sets the room name\nclass Room {\n    let name: String\n    init(name: String) { self.name = name }\n}\n\n// The final class in the model is called Address\nclass Address {\n    var buildingName: String?\n    var buildingNumber: String?\n    var street: String?\n    func buildingIdentifier() -> String? {\n        if (buildingName != nil) {\n            return buildingName\n        } else if (buildingNumber != nil) {\n            return buildingNumber\n        } else {\n            return nil\n        }\n    }\n}\n\nlet john = Person()\nif ((john.residence?.printNumberOfRooms()) != nil) {\n    print("Print room number")\n} else {\n    print("Unable to print room number")\n}
\n\nThe output of the above program execution is:\n\n
Unable to print room number
\n\nUsing an if statement to check whether the printNumberOfRooms method can be called successfully: if the method is called successfully through optional chaining, the implicit return value of printNumberOfRooms will be Void; if it is not successful, it will return nil.\n\n* * *\n\n## Calling Subscripts Using Optional Chaining\n\nYou can use optional chaining to try to get a value from a subscript and check whether the subscript call is successful; however, you cannot use optional chaining to set a subscript.\n\n### Instance 1\n\n
class Person {\n    var residence: Residence?\n}\n\n// Defines a variable rooms, which is initialized as a Room[]empty array of type\nclass Residence {\n    var rooms = ()\n    var numberOfRooms: Int {\n        return rooms.count\n    }\n    subscript(i: Int) -> Room {\n        return rooms\n    }\n    func printNumberOfRooms() {\n        print("Room number is \\(numberOfRooms)")\n    }\n    var address: Address?\n}\n\n// Room Defines a name property and an initializer that sets the room name\nclass Room {\n    let name: String\n    init(name: String) { self.name = name }\n}\n\n// The final class in the model is called Address\nclass Address {\n    var buildingName: String?\n    var buildingNumber: String?\n    var street: String?\n    func buildingIdentifier() -> String? {\n        if (buildingName != nil) {\n            return buildingName\n        } else if (buildingNumber != nil) {\n            return buildingNumber\n        } else {\n            return nil\n        }\n    }\n}\n\nlet john = Person()\nif let firstRoomName = john.residence?.name {\n    print("First room name \\(firstRoomName).")\n} else {\n    print("Unable to retrieve room")\n}
\n\nThe output of the above program execution is:\n\n
Unable to retrieve room
\n\nIn the subscript call, the question mark of the optional chain is placed directly after john.residence and before the subscript brackets, because john.residence is the optional value that the optional chain is trying to access.\n\n### Instance 2\n\nIn the instance, if you create a Residence instance for john.residence, and there is one or more Room instances in his rooms array, then you can use optional chaining through the Residence subscript to get the instances in the rooms array:\n\n
class Person {\n    var residence: Residence?\n}\n\n// Defines a variable rooms, which is initialized as a Room[]empty array of type\nclass Residence {\n    var rooms = ()\n    var numberOfRooms: Int {\n        return rooms.count\n    }\n    subscript(i: Int) -> Room {\n        return rooms\n    }\n    func printNumberOfRooms() {\n        print("Room number is \\(numberOfRooms)")\n    }\n    var address: Address?\n}\n\n// Room Defines a name property and an initializer that sets the room name\nclass Room {\n    let name: String\n    init(name: String) { self.name = name }\n}\n\n// The final class in the model is called Address\nclass Address {\n    var buildingName: String?\n    var buildingNumber: String?\n    var street: String?\n    func buildingIdentifier() -> String? {\n        if (buildingName != nil) {\n            return buildingName\n        } else if (buildingNumber != nil) {\n            return buildingNumber\n        } else {\n            return nil\n        }\n    }\n}\n\nlet john = Person()\nlet johnsHouse = Residence()\njohnsHouse.rooms.append(Room(name: "Living Room"))\njohnsHouse.rooms.append(Room(name: "Kitchen"))\njohn.residence = johnsHouse\n\nlet johnsAddress = Address()\njohnsAddress.buildingName = "The Larches"\njohnsAddress.street = "Laurel Street"\njohn.residence!.address = johnsAddress\n\nif let johnsStreet = john.residence?.address?.street {\n    print("John located inThe street is \\(johnsStreet)。")\n} else {\n    print("Unable to retrieve address. ")\n}
\n\nThe output of the above program execution is:\n\n
John located in
← Swift ArcSwift Deinitialization β†’