티스토리 뷰
Optional Chaining — The Swift Programming Language (Swift 5.5)
Optional Chaining Optional chaining is a process for querying and calling properties, methods, and subscripts on an optional that might currently be nil. If the optional contains a value, the property, method, or subscript call succeeds; if the optional is
docs.swift.org
1차 수정 (2022 / 02 / 27) : 의역, 오타 및 스타일 수정
Optional Chaining
옵셔널 체이닝(optional chaining)은 지금 nil 일 것으로 예상되는 옵셔널 프로퍼티, 메소드, 서브스크립트를 요청하고 호출하는 과정입니다. 만약 옵셔널 값을 가진다면 프로퍼티, 메소드, 서브스크립트 호출은 성공입니다. 만약 옵셔널이 nil 이라면, 프로퍼티, 메소드, 서브스크립트 호출은 실패입니다. 여러 요청을 한 번에 묶을 수 있으며, 체인의 전체 링크(link) 가 nil 이면 모든 체인이 실패합니다.
Swift의 옵셔널 체이닝은 Objective-C의 nil 메시지와 유사사하지만 모든 타입에 대해 작동하며 성공인지 실패인지를 확인할 수 있습니다.
Optional Chaining as an Alternative to Forced Unwrapping (강제 언래핑 대안으로써 옵셔널 체이닝)
non-nil 의 옵셔널 값을 가지는 프로퍼티, 메소드, 서브스크립트를 호출하고 싶으면, 옵셔널 값 ? 뒤에 옵셔널 체인닝을 사용합니다. 이것은 옵셔널 값을 강제 언래핑하기 위해 !를 사용하는 것과 매우 유사합니다. 가장 큰 차이점은 옵셔널 값이 nil일 때, 옵셔널 체이닝은 친절하게 실패를 나타내줍니다. 그에 반해 강제 언래핑은 옵셔널 값이 nil이면 런타임 에러를 발생시킵니다.
옵셔널 체이닝이 nil 값을 호출할 수 있는 것을 반영하기 위해서, 옵셔널 체이닝 호출의 결과는 항상 옵셔널 값입니다. 프로퍼티, 메소드, 서브스크립트가 옵셔널이 아닌 값을 리턴하는 경우에도 마찬가지입니다. 옵셔널 체이닝 호출이 성공했는지, nil 값을 받아 실패했는지를 확인하기 위해 옵셔널 리턴 값을 사용합니다.
옵셔널 체이닝 호출 결과는 기대했던 리턴 값과 같은 타입이지만 옵셔널로 감싸져 있습니다. Int를 리턴하는 프로퍼티는 옵셔널 체이닝에 의해 Int?를 리턴할 것 입니다.
다음 예제들은 옵셔널 테이닝이 강제 언래핑과 어떻게 다른지 보여줍니다. 옵셔널 체이닝은 성공 여부를 확인 할 수 있습니다.
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
Residence 인스턴스는 `기본값 1을 가지는 Int 프로퍼티 numberOfRooms `를 가집니다. Person 인스턴스는 Residence? 타입의 옵셔널 residence 프로퍼티를 가집니다.
만약 새로운 Person 인스턴스를 생성한다면, residence 프로퍼티는 nil로 기본값이 초기화됩니다.
let john = Person()
john의 residence 프로퍼티는 nil 값을 가집니다. 만약 residence의 nuimberOfRooms에 접근하려고 하면 residence를 강제 언래핑 할 것 입니다. 하지만 residence는 언래핑할 값이 없기 때문에 런타임 에러가 발생합니다.
let roomCount = john.residence!.numberOfRooms
// this triggers a runtime error
위 코드는 john.residence가 nil이 아닌 값을 가지고, numberOfRooms 을 적절한 Int 값으로 설정하면 성공합니다. 그러나 residence가 nil일 때마다 런타임에러를 발생시킬 것 입니다.
옵셔널 체이닝은 numberOfRooms 에 접근하기 위한 대안을 제공합니다. ! 대신에 ? 를 작성해 옵셔널 체이닝을 사용할 수 있습니다.
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에게 옵셔널 residence 프로퍼티를 "chain" 하고 residence 값이 존재할 경우 numberOfRooms 를 검색하도록 합니다.
numberOfRooms 에 대한 접근이 실패할 가능성이 있기 때문에, 옵셔널 체이닝은 Int? 혹은 `옵셔널 Int` 값 리턴을 시도합니다. 위 예제처럼 residence가 nil 일때 이 옵셔널 Int 도 nil 일 것이고, 이 사실을 반영하기 위해 numberOfRooms 에 대한 접근을 불가능하게 합니다. 옵셔널 Int 는 오셔널 바인딩을 통해 접근되고 옵셔널이 아닌 상수 값을 할당받습니다.
john.residence = Residence()
john.residence에 Residence 인스턴스를 할당할 수 있습니다. 이제 residence는 nil 값을 갖지 않습니다.
john.residence는 실제 Residence 인스턴스를 포함하고 있습니다. 전과 같은 옵셔널 체이닝으로 numberOfRooms 에 접근하려고 하면, 기본값 1을 가지는 numberOfRooms 를 Int? 로 리턴합니다.
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)."
Defining Model Classes for Optional Chaining(옵셔널 체이닝을 위한 모델 클래스 정의)
한 단계 이상 깊은 프로퍼티, 메소드, 서브스크립트를 호출하기 위해 옵셔널 체이닝을 사용할 수 있습니다. 이것은 상호연결된 타입의 복잡한 모델 안에서 하위 프로퍼티를 드릴 다운(drill down) 하고 하위 프로퍼티의 프로퍼티, 메소드, 서브스크립트에 대한 접근 여부를 확인할 수 있습니다
드릴 다운(drill down) : 문제를 여럿으로 세분화하면서 분석하는 기법
아래 코드 예제를 통해 몇 가지 후속 예제에서 사용할 수 있는 네 가지 모델 클래스와 여러 단계의 옵셔널 체이닝 예제를 정의합니다.
class Person {
var residence: Residence?
}
Residence 클래스는 rooms 이라는 변수 프로퍼티를 가집니다. rooms 프로퍼티는 [Room] 타입의 빈 배열로 초기화 됩니다.
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?
}
예제의 Residence 는 Room 인스턴스의 배열을 저장하기 때문에 numberOfRooms 프로퍼티는 저장 프로퍼티가 아닌 연산 프로퍼티(computed property)로 실행됩니다. 연산 프로퍼티 numberOfRooms 는 rooms 배열로부터 count 프로퍼티 값을 리턴합니다.
rooms 배열에 빠르게 접근하기 위해 Residence는 `room 배열에서 요청한 index로 접근할 수 있는 읽기-쓰기(read-write) 서브스크립트` 를 제공합니다.
Residence는 Address? 타입으로써 옵셔널 프로퍼티 address를 정의합니다.
rooms 배열을 위해 사용되는 Room 클래스는 name 프로퍼티와 이를 적절한 방 이름으로 초기화하는 이니셜라이져를 가집니다.
class Room {
let name: String
init(name: String) { self.name = name }
}
마지막 클래스는 Address 입니다. String? 타입을 가지는 3개의 옵셔널 프로퍼티를 가집니다.
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
}
}
}
Address 는 String? 타입을 리턴하는 buildingIdentifier()라는 메소드도 제공합니다. 이 메소드는 주소의 프로퍼티를 확인합니다. 값을 가지고 있으면 bulidingName 을 리턴하거나 street, buildingNumber 이 두 프로퍼티 들이 값을 가지는지 확인합니다. 값이 없을 경우 nil 입니다.
정의한 이 4가지 모델에 이제 옵셔널체이닝에 적용해 보겠습니다.
Accessing Properties Through 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."
위에서 정의된 클래스를 사용해 새로운 Person 인스턴스를 생성하고 numberOfRooms에 접근하려고 시도합니다. 하지만 john.residence 가 nil 이기 때문에, 옵셔널 체이닝 호출은 실패합니다.
옵셔널 체이닝을 통해 프로퍼티 값 설정을 시도할 수 있습니다.
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
john.residence?.address = someAddress
john.residence 가 nil 이기 때문에, john.residence의 address 프로퍼티를 설정하려는 시도는 실패합니다.
할당은 옵셔널 체이닝의 일부입니다. 즉, = 연산의 오른쪽에 있는 코드는 평가되지 않습니다. 앞선 예제에서 someAddress가 평가되지 않는 것을 보는 것은 쉽지 않습니다. 상수에 대한 접근은 어떤 부가 효과 발생시키지 않기 때문입니다. 아래 예제는 같은 할당이지만, 함수를 써서 Address를 생성합니다. 함수는 Address()를 할당하기 전에 print문을 작성합니다.
func createAddress() -> Address {
print("Function was called.")
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"
return someAddress
}
john.residence?.address = createAddress()
아무 것도 출력되지 않았고, createAddress() 함수가 호출되지 않았음을 알 수 있습니다. 이를 통해 옵셔널 체이닝의 = 오른쪽에 있는 코드는 실행되지 않는 것을 확인했습니다.
Calling Methods Through Optional Chaining(옵셔널 체이닝을 통한 메소드 호출)
옵셔널 값으로 메소드를 호출하고 메소드 호출의 성공여부를 확인하기 위해 옵셔널 체이닝을 사용할 수 있습니다. 메소드가 리턴 값을 정의하지 않아도 사용할 수 있습니다.
Residence 클래스의 printNumberOfRooms() 메소드는 numberOfRooms 의 현재 값을 출력합니다.
func printNumberOfRooms() {
print("The number of rooms is \(numberOfRooms)")
}
이 메소드는 리턴 타입이 없습니다. 그러나 리턴 타입이 없는 함수와 메소드는 암시적으로 Void 타입을 리턴합니다. () 나 빈 튜플을 리턴합니다.
옵셔널 체이닝을 사용해 이 메소드를 옵셔널 값을 호출한다면, 메소드의 리턴 타입은 Void? 일 것입니다. 왜냐하면 옵셔널 체이닝이 호출한 리턴 값은 항상 옵셔널 타입이기 때문입니다. 이를 통해 메소드가 리턴 값을 정의하지 않더라도, if 문을 사용해 printNumberOfRooms()를 호출할 수 있는지 확인할 수 있습니다. printNumberOfRooms 호출의 리턴 값을 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."
옵셔널 체이닝을 통해 프로퍼티를 설정할 때도 똑같습니다. 옵셔널 체이닝을 통해 프로퍼티를 설정하려고 하면 Void? 값이 리턴됩니다. 이 값을 0 과 비교하여 프로퍼티가 성공적으로 설정되었는지 확인할 수 있습니다.
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."
Accessing Subscripts Through Optional Chaining (옵셔널 체이닝을 통한 서브스크립트 접근)
서브스크립트의 값을 옵셔널 값으로 검색하고 설정하는 것을 시도하기 위해 옵셔널 체이닝을 사용할 수 있습니다. 서브스크립트 호출이 성공적인지 확인 할 수도 있습니다.
옵셔널 체이닝을 통해 서브스크립트에 접근할 때 ? 를 [ ] 이전에 사용합니다. 옵셔널 체이닝 물음표는 항상 옵셔널 표현식 뒤에 바로 따라옵니다.
아래 예제는 Residence 클래스에 정의된 서브스크립트를 사용해, john.residence 프로퍼티의 rooms 배열에 첫 번째 방 이름을 검색하는 것을 시도합니다. 현재 john.residence 가 nil 이기 때문에 서브스크립트는 실패합니다.
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."
옵셔널 체이닝 ? 는 john.residence 바로 뒤에, [] 앞에 작성되없습니다. john.residence가 옵셔널 체이닝을 시도하는 옵셔널 값이기 때문입니다.
비슷하게 옵셔널 체이닝을 통해 서브스크립트에 새로운 값 설정을 시도할 수 있습니다.
john.residence?[0] = Room(name: "Bathroom")
현재 residence 가 nil이기 때문에 위 예제도 실패합니다.
rooms 배열에 한 개 이상의 Room 인스턴스를 가지는 Reisdence 인스턴스를 생성하고 john.residence에 할당하면, Reisdence 서브스크립트를 사용해 실제 room 배열에 있는 항목에 접근할 수 있습니다.
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."
Accessing Subscripts of Optional Type (옵셔널 타입 서브스크립트 접근)
서브스크립트가 옵셔널 타입의 값을 리턴한다면 서브스크립트의 [] 뒤에 ?를 사용합니다. (Swift의 Dictionary 타입)
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]
예제는 testScores 라는 dictionary를 정의합니다. "Dave" 배열의 첫 번째 항목 설정하고 "Bev" 배열의 첫 번째 항목을 설정하기위해서 옵셔널 체이닝을 사용합니다. testScores 가 "Dave" 와 "Bev" 라는 키 값을 가지므로 이 호출들은 성공합니다. 하지만 "Brian" 배열에 접근하기 위해 옵셔널 체이닝을 사용했을 때, testScores 가 "Brian" 값을 가지지 않으므로 세 번째 호출은 실패합니다.
Linking Multiple Levels of Chaining (여러 레벨의 체이닝 연결)
프로퍼티, 메소드, 서브스크립트를 더 깊게 드릴 다운(drill down) 하기위해 여러 레벨의 옵셔널 체이닝을 함께 연결할 수 있습니다. 그러나 여러 레벨의 옵셔널 체이닝은 리턴 값에 더 많은 단계의 옵셔널(optionality)을 추가하지 않습니다.
달리 말하자면:
- 검색하고자 하는 타입이 옵셔널이 아니면, 옵셔널 체이닝 때문에 옵셔널 타입이 된다
- 검색하고자 하는 타입이 이미 옵셔널이라면, 옵셔널 체이닝 때문에 `더` 옵셔널이 되지 않는다.
더하여
- 옵셔널 체이닝을 통해 Int 값을 검색한다면, 체이닝이 몇 레벨 사용되었는지와 상관없이 Int? 가 항상 리턴됩니다.
- 옵셔널 체이닝을 통해 Int? 값을 검색한다면, 체이닝이 몇 레벨 사용되었는지와 상관없이 Int? 가 항상 리턴됩니다.
아래 예제는 john의 residence.address.street 프로퍼티에 접근을 시도합니다. 두 레벨의 옵셔널 체이닝이 사용됩니다.
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."
john.residence는 유효한 Residence 인스턴스를 가지지만, john.residence.address 가 현재 nil 입니다. 따라서 john.residence.address.street 호출은 실패합니다.
예제에서 street 프로퍼티에 접근합니다. 이 프로퍼티의 타입은 String? 입니다. 옵셔널 체이닝이 2번 사용되었지만 john.residence?.address?.street 도 String? 타입을 가집니다.
만약 john.residence.address에 유효한 Address 인스턴스를 설정하고 street 프로퍼티에 실제 값을 설정하면, 여러 레벨의 옵셔널 체이닝을 통해 street 프로퍼티에 접근할 수 있습니다.
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."
Chaining on Methods with Optional Return Values (옵셔널 리턴 값을 가지는 메소드 체이닝)
앞선 예제들은 어떻게 옵셔널 체이닝을 통해 옵셔널 타입의 값을 검색하는지 보여주었습니다. 옵셔널 타입을 가지는 메소드를 호출하고 필요에 따라 해당 메소드의 리턴값을 묶기 위해, 옵셔널 체이닝을 사용할 수 있습니다.
아래 예제는 옵셔널 체이닝으로 Address의 클래스 buildingIdentifier()를 호출합니다. 이 메소드는 String? 타입을 반환합니다. 옵셔널 체이닝 후에 이 메소드 호출의 리턴 타입도 String? 입니다.
if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
print("John's building identifier is \(buildingIdentifier).")
}
// Prints "John's building identifier is The Larches."
이 메소드 리턴 값에 더 많은 옵셔널 체이닝을 사용하고 싶으면 메소드의 () 뒤에 옵셔널 체이닝의 ? 을 사용합니다.
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"."
위의 예제에서 () 뒤에 ?를 작성했습니다. buildingIdentifier() 메소드가 아니라 buildingIdentifier() 메소드의 리턴 값에 옵셔널 체이닝을 사용했기 때문입니다.
옵셔널 체이닝을 통해 옵셔널 값들을 언래핑 할 수 있다.
언래핑 결과과 성공인지 실패인지 알 수 있다.
옵셔널 체이닝을 여러번 사용해도 그냥 옵셔널 타입이다.
ex) 옵셔널 체이닝을 여러번 사용한다고 Int 가 Int???? 가 되지 않는다. 그냥 Int? 타입이다
옵셔널 체이닝 끗😁
'Swift > Swift 공식문서' 카테고리의 다른 글
[Swift] 공식문서 18 - Concurrency(동시성) (0) | 2021.10.30 |
---|---|
[Swift] 공식문서 17 - Error Handling(에러 처리) (0) | 2021.10.29 |
[Swift] 공식문서 15 - Deinitialization (초기화 해제) (0) | 2021.10.27 |
[Swift] 공식문서 14 - Initialization (초기화) (0) | 2021.10.26 |
[Swift] 공식문서 13 - Inheritance (상속) (0) | 2021.10.24 |
- Total
- Today
- Yesterday
- 필독서
- 앱개발
- vapor
- 책리뷰
- 책후기
- 책
- UX
- Swift
- 코딩테스트
- 개발
- TODO
- Swift공식문서
- ios
- Swift 디자인 패턴
- 날씨어플
- 부스트캠프7기
- todo앱
- 코딩 테스트
- Swift문법
- Swift 서버
- 프로그래머스
- Swift DocC
- 부스트캠프iOS
- 부스트캠프
- SwiftUI
- 애플
- 코딩
- Combine
- swiftUI 기초
- 디자인 패턴
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |