Search

[Swift 공식 문서] 16. Optional Chaining

Optional chaining 이란 nil 값일지도 모르는 optional 타입에 대해 프로퍼티, 메서드, 첨자를 호출하는 과정이다.
optional 값이 존재한다면, 호출에 성공한다
optional 값이 nil 이라면, 해당 호출은 nil 값을 리턴한다.
여러개의 query 들을 연속적으로 사용할 수 있다.

Optional Chaining as an Alternative to Forced Unwrapping

프로퍼티, 메서드, 첨자의 리턴타입과는 상관없이 optional chaining 은 optional 타입을 리턴하며, nil 값은 optional chaining 과정이 실패하였음을 의미한다.
optional 값 뒤에 ? 를 작성함으로써 optional chaining 을 사용할 수 있다.
이는 ! 를 활용한 Forced unwrap 과 유사하나, forced unwrap 은 runtime error 를 발생시킨다.
프로퍼티, 메서드의 리턴 타입과는 상관없이 optional chaining 의 결과는 항상 optional 타입이다.
리턴 되는 optional 타입이 nil 인지를 확인해 optional chaining 의 결과가 성공적이었는지 확인이 가능하다.
class Person { var residence: Residence? } class Residence { var numberOfRooms = 1 } let john = Person() let roomCount = john.residence!.numberOfRooms // this triggers a runtime error
Swift
복사
nil 값인 프로퍼티에 ! 사용시 runtime error 발생
if let roomCount = john.residence?.numberOfRooms { print("John's residence has \(roomCount) room(s).") } else { print("Unable to retrieve the number of rooms.") } // Prints "Unable to retrieve the number of rooms."
Swift
복사
위와 같이 optional chaining 을 사용하면 안전하게 프로퍼티에 접근 가능.
optional chaining 은 항상 optional 한 값을 리턴하므로 이 값을 사용하려면 optional을 벗겨주어야 한다.
위 예제에서는 optional binding 을 사용하여 optional 의 값을 사용해 주었다.
주의깊게 볼 점은 numberOfRooms 프로퍼티는 optional 타입이 아니라는 점이다.
john.residence = Residence() if let roomCount = john.residence?.numberOfRooms { print("John's residence has \(roomCount) room(s).") } else { print("Unable to retrieve the number of rooms.") } // Prints "John's residence has 1 room(s)."
Swift
복사
john 의 residence 프로퍼티가 더이상 nil 이 아니다.

Defining Model Classes for Optional Chaining

한 레벨 이상의 깊이에 존재하는 메서드, 프로퍼티, 서브 스크립트에도 옵셔널 체이닝을 사용할 수 있다. 복잡한 구조를 가진 모델에서도 하위 프로퍼티들에 접근해서 값이 있는지를 확인할 수 있다. 이때 체인 중 하나라도 nil을 반환하면 실패하게 된다.
class Person { var residence: Residence? } class Room { let name: String init(name: String) { self.name = name } } class Residence { var rooms: [Room] = [] var numberOfRooms: Int { return rooms.count } subscript(i: Int) -> Room { get { return rooms[i] } set { rooms[i] = newValue } } func printNumberOfRooms() { print("The number of rooms is \(numberOfRooms)") } var address: Address? } class Address { var buildingName: String? var buildingNumber: String? var street: String? func buildingIdentifier() -> String? { if let buildingNumber = buildingNumber, let street = street { return "\(buildingNumber) \(street)" } else if buildingName != nil { return buildingName } else { return nil } } }
Swift
복사

Accessing Properties Through Optional Chaining

이제 위에서 정의한 프로퍼티들에 optional chaining 을 이용해 접근해보자
let john = Person() if let roomCount = john.residence?.numberOfRooms { print("John's residence has \(roomCount) room(s).") } else { print("Unable to retrieve the number of rooms.") } // Prints "Unable to retrieve the number of rooms."
Swift
복사
let someAddress = Address() someAddress.buildingNumber = "29" someAddress.street = "Acacia Road" john.residence?.address = someAddress
Swift
복사
위에서 john.residence 가 현재 nil 값이므로 someAddress 의 할당은 실패한다.
optional chaining 이 실패하게 되면 = 오른쪽의 코드는 실행되지 않는다.
func createAddress() -> Address { print("Function was called.") let someAddress = Address() someAddress.buildingNumber = "29" someAddress.street = "Acacia Road" return someAddress } john.residence?.address = createAddress()
Swift
복사
마찬가지로 createAddress() 함수는 호출되지 않는다.

Calling Methods Through Optional Chaining

optional 값의 메서드를 호출하는데 optional chaining 을 사용할 수 있다.
메서드가 어떠한 값도 리턴하지 않는 경우에도 optional chaining 이 실패하면 nil 값을 리턴하게된다.
이를 활용해 성공적으로 메서드가 호출되었는지를 확인할 수 있다.
if john.residence?.printNumberOfRooms() != nil { print("It was possible to print the number of rooms.") } else { print("It was not possible to print the number of rooms.") } // Prints "It was not possible to print the number of rooms."
Swift
복사
optional chaining 을 통해 새로운 값을 설정하고자 할 때도 마찬가지로 성공 여부를 확인할 수 있다.
if (john.residence?.address = someAddress) != nil { print("It was possible to set the address.") } else { print("It was not possible to set the address.") } // Prints "It was not possible to set the address."
Swift
복사

Accessing Subscripts Through Optional Chaining

optional chaining 을 첨자접근에 사용할 수 있습니다.
첨자 접근시 대괄호 앞에 ? 를 작성합니다
if let firstRoomName = john.residence?[0].name { print("The first room name is \(firstRoomName).") } else { print("Unable to retrieve the first room name.") } // Prints "Unable to retrieve the first room name."
Swift
복사
첨자접근에 optional chaining 을 사용해서 접근이 성공적으로 이루어졌는지도 확인할 수 있습니다.
john.residence?[0] = Room(name: "Bathroom")
Swift
복사
유사하게 첨자접근을 활용한 값 할당에도 optional chaining 을 사용할 수 있습니다.
let johnsHouse = Residence() johnsHouse.rooms.append(Room(name: "Living Room")) johnsHouse.rooms.append(Room(name: "Kitchen")) john.residence = johnsHouse if let firstRoomName = john.residence?[0].name { print("The first room name is \(firstRoomName).") } else { print("Unable to retrieve the first room name.") } // Prints "The first room name is Living Room."
Swift
복사

Accessing Subscripts of Optional Type

만약 첨자 접근이 optional 타입의 값을 리턴한다면, Swift 의 Dictionary 타입의 key 첨자 같은 곳에도 optional chaining 이 가능합니다
var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]] testScores["Dave"]?[0] = 91 testScores["Bev"]?[0] += 1 testScores["Brian"]?[0] = 72 // the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81]
Swift
복사
Brian 이라는 key 는 존재하지 않으므로 할당과정이 실행되지 않습니다.

Linking Multiple Levels of Chaining

optional chaining 을 연달아 사용할 수 있습니다.
연달아 사용한다고 optional 에 optional 이 덧 씌워지는 건 아닙니다.
정리하자면
불러오고자 하는 값이 optional type 이 아닌 경우 optional chaining 으로 인해 optional 타입으로 리턴됩니다
이미 optional 인 값에 대해서는 그대로 optional 타입이 리턴됩니다.
optional chaining 과정을 통해 Int 값을 불러온다고 했을 때, 리턴 타입은 Int? 입니다
optional chaining 과정을 통해 Int? 값을 불러온다고 했을 때, 리턴 타입은 Int? 입니다
if let johnsStreet = john.residence?.address?.street { print("John's street name is \(johnsStreet).") } else { print("Unable to retrieve the address.") } // Prints "Unable to retrieve the address."
Swift
복사
두 단계의 optional chaining 이 사용 되었지만 리턴은 optional 타입입니다.
위 예제에서는 address 프로퍼티가 nil 값이므로 else 문이 실행됩니다,.
let johnsAddress = Address() johnsAddress.buildingName = "The Larches" johnsAddress.street = "Laurel Street" john.residence?.address = johnsAddress if let johnsStreet = john.residence?.address?.street { print("John's street name is \(johnsStreet).") } else { print("Unable to retrieve the address.") } // Prints "John's street name is Laurel Street."
Swift
복사
address 프로퍼티를 할당해 줌으로서
optional chaining 을 통해 해당 프로퍼티에 접근이 가능해지는 모습

Chaining on Methods with Optional Return Values

optional 타입의 값을 리턴하는 메서드를 optional chaining 을 통해서 호출해보자.
if let buildingIdentifier = john.residence?.address?.buildingIdentifier() { print("John's building identifier is \(buildingIdentifier).") } // Prints "John's building identifier is The Larches."
Swift
복사
만약 이 리턴값에 대해 추가적인 메서드를 호출하고자 한다면 괄호 뒤에 ? 를 작성해서 추가적으로 optional chaining 해주면 된다.
if let beginsWithThe = john.residence?.address?.buildingIdentifier()?.hasPrefix("The") { if beginsWithThe { print("John's building identifier begins with \"The\".") } else { print("John's building identifier doesn't begin with \"The\".") } } // Prints "John's building identifier begins with "The"."
Swift
복사