forked from lean-tra/Swift-Korean
-
Notifications
You must be signed in to change notification settings - Fork 1
/
chapter19.txt
230 lines (214 loc) · 17.7 KB
/
chapter19.txt
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# 19 선택 묶임 (Optional Chaining)
> Translator : 허혁 ([email protected])
선택 묶임(Optional chaining)란 nil이 될 수 있는 선택지(options)를 가진 프로퍼티(property), 메소드(method), 서브 스크립트 (subscript)에 질의하고 호출하는 프로세스를 말한다. 만약 어떤 선택지가 값을 가진다면 프로퍼티, 메소드, 서브스크립트 호출은 성공하고 선택지가 nil이면, 프로퍼티, 메소드, 서브스크립트 호출은 nil을 반환하게 된다.
여러개의 질의도 함께 엮일 수 있으며, 만약 묶임(chaining) 중간의 어떤 링크가 nil이라면 조용하게 전체 체인은 실패한다.
>노트
스위프트(Swift)의 선택 묶임가 오브젝티브씨(Objective-C)에 있는 nil에 메시지 보내기와 유사하다. 그러나, 모든 타입(any type)에서 동작하고, 성공, 실패 여부를 확인할 수 있다는 점에서 차이가 있다.
## 강제 랩핑 해제(Forced Unwrapping) 대안으로써 선택 묶임
호출하고자 하는 프로퍼티, 메소드, 서브스크립트의 선택지 값(optional value)이 nil 아닐 때 선택지 값 뒤에 물음표(?)를 두어 선택 묶임를 둘 수 있다. 이것은 선택지 값 뒤에 느낌표(!)를 두어 그 값을 강제로 랩핑 해제하는 것과 유사하다. 가장 주요한 차이점은 선택 묶임는 선택지가 nil일 때 자연스럽게 실패한다는 것이고, 강제 랩핑 해제는 선택지가 nil인 경우 런타임 에러가 발생한다.
선택 묶임가 nil 값에도 호출할 수 있다는 사실을 반영하기 위해 선택 묶임 호출 결과는 항상 선택지 값이다. 비록 질의한 프로퍼티, 메소드, 서브스크립트가 항상 선택지 값이 아닌 결과를 도출해도 그렇다. 이 선택지 반환 값을 사용해서 선택 묶임 호출이 성공했는지 ( 반환된 선택지가 값을 가지는 ) 묶임 중간의 nil 값 ( 선택지 반환값이 nil ) 때문에 실패했는지를 확인할 수 있다.
구체적으로, 선택 묶임 호출 결과는 선택지로 감싸여져 있음에도 기대한 반환값과 동일한 타입이다. 일반적으로 Int를 반환하는 프로퍼티는 선택 묶임에 따라 접근이 가능할때는 Int?를 반환할 것이다.
다은 몇몇 코드 조각은 선택 묶임가 어떻게 강제 랩핑 해제와 다르고 성공 여부 확인을 가능케 하는지 보여준다.
먼저 Person과 Residence 라는 클래스를 정의하자.
```
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}]
```
Residence 인스턴스(Instance)는 기본값이 1인 numberOfRooms 이라는 단 하나의 Int 프로퍼티를 가진다. Person 인스턴스는 Residence? 타입으로 residence 이라는 선택적 프로퍼티를 가진다.
만약 Person 이라는 인스턴스를 새로 만들면, 선택지가 된 효과에 따라 기본적으로 nil로 설정된다. 아래 코드에서는 john는 nil로 된 residence 프로퍼티를 가질 것이다.
let jone = Person()
만약 Person의 residence의 numberOfRooms 프로퍼티를 그 값을 강제로 랩핑 해제를 하려고 느낌표를 붙여서 접근한다면 런타임 에러(Runtime Error)를 유발시킬 것이다. 왜냐하면 해제할 residence 값 자체가 없기 때문이다.
let roomCount = john.residence.numberOfRooms
위 코드는 john.residence가 nil이 아닌 값을 성공하며 방 갯수에 적절한 숫자를 담고 있는 Int 값에 roomCount를 설정할 것이다. 그러나 이 코드는 위에 보여지는 것처럼 residence가 nil이라면 항상 런타임 에러를 유발 시킨다.
선택 묶임는 numberOfRooms 값에 접근하는데 대안법을 제공한다. 선택 묶임를 사용하기 위해 느낌표 자리에 물음표를 사용하면 된다.
```
if let roomCount = john.residence?.numberOfRooms {
println("John's residence has \(roomCount) room(s).")
} else {
println("Unable to retrieve the number of rooms.")
}
// prints "Unable to retrieve the number of rooms."
```
이것은 스위프트(swift)가 선택적 residence 프로퍼티를 "엮고" 만약 residence가 있으면 numberOfRooms 값을 가져온다는 것을 말해준다.
numberOfRooms에 대한 접근이가 잠제적으로 실패할 수 있기 때문에 선택 묶임는 Int?이나 "선택적 Int"형 값을 반환하려고 한다. 위 예제처럼 residence가 nil인 경우는 numberOfRooms에 대한 접근이 불가능하다는 사실을 반영하기 위해서 이 선택적 Int 역시 nil이 될 것이다.
numberOfRooms가 비선택적 Int 임에도 불구하고 참인 것을 명심해라. 선택 묶임를 통해 질의한다는 것은 numberOfRooms가 Int 대신 Int?를 항상 반환할 것이라는 것을 의미한다.
john.residence에 Residence 인스턴스를 할당할 수 있는데 그러면 더이상 nil 값은 존재하지 않게 된다.
```
john.residence = Residence()
```
john.residence는 실체 Residence 인스턴스를 이제 가지게 되었다. 만약 예전과 동일한 선택 묶임를 사용해 접근하려고 하면, 1이라는 numberOfRooms 기본값을 가지는 Int?가 반환될 것이다.
```
if let roomCount = john.residence?.numberOfRooms {
println("John's residence has \(roomCount) room(s).")
} else {
println("Unable to retrieve the number of rooms.")
}
// prints "John's residence has 1 room(s)."
```
## 선택 묶임을 위한 모델(Model) 클래스(Class) 선언
프로퍼티, 메소드, 서브스크립트를 호출하는 것 같은 한단계 더 깊은 선택 묶임을 사용할 수 있다. 이는 상호관계있는 타입간의 복잡한 모델에서 서브 프로퍼티(subproperty)를 파고 들 수 있게 해주고 그 서브 프로터티에 프로퍼티와 메소드, 서브스크립트에 접근할 수 있는지 아닌지를 확인할 수 있게 해준다.
다음 코드 조각은 다단계 선택 묶임 예를 포함한 몇가지 순차적인 예제에서 사용될 4개의 모델 클래스를 정의한다. 이 클래스들은 위에 나온 Person과 Residence 모델에 Room과 Address 클래스를 추가하고 연관 프로퍼티와 메소드, 서브스크립트를 확장한다.
Person 클래스는 이전과 동일한 방법으로 정의한다.
```
class Person {
var residence: Residence?
}
```
Residence 클래스는 이전보다 조금 복잡해졌다. 이번에는 Residence 클래스에 Room[] 타입의 빈 배열로 초기화된 rooms라는 변수 프로퍼티를 선언한다.
```
class Residence {
var rooms = Room[]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
return rooms[i]
}
func printNumberOfRooms() {
println("The number of rooms is \(numberOfRooms)")
}
var address: Address?
}
```
이번 버전 Residence는 Room 인스턴스 배열을 저장하기 때문에, 그 numberOfRooms 프로퍼티는 저장된 프로퍼티가 아닌 계산된 프로퍼티로 구현했다. 계산된 numberOfRooms 프로퍼티는 단순히 rooms 배열에서 count 프로퍼티의 값을 반환한다.
그 rooms 배열에 접근하기 위한 바로가기로 이번 버전 Residence는 읽기만 가능한 서브 스크립트를 제공하는데 서브스크립트에게 전달받는 인덱스(index)가 적합할 것이라는 가정으로 시작해보겠다. 만약 인덱스가 적합하다면, 서브스크립트는 rooms 배열의 요청받은 인덱스의 방정보를 반환할 것이다.
또한 이번 버전 Residence는 printNumberOfRooms라는 이름의 메소드를 제공하는데 단순히 Residence에 방 갯수를 출력한다.
마지막으로 Residence에 Address?이란 타입으로 address라는 선택적 프로퍼티를 선언한다. 이를 위한 Address 클래스 타입은 밑에 정의하겠다.
rooms 배열에 사용하는 Room 클래스는 name이라는 프로퍼티 하나를 가지는 간단한 클래스인데 이는 적절한 방이름을 설정하기 위한 초기화 역할(initializer)을 한다.
```
class Room {
let name: String
init(name: String) { self.name = name }
}
```
이 모델의 마지막 클래스는 Address이다. 이 클래스는 String? 타입의 선택적 프로퍼티를 3개 가지고 있다. 그 중 2개는 buildingName과 buildingNumber 인데 주소를 구성하는 특정 빌딩에 대한 구분을 짓기 위한 대체 수단이다. 3번째 프로퍼티인 street는 그 주소의 도로이름에 사용한다.
```
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if buildingName {
return buildingName
} else if buildingNumber {
return buildingNumber
} else {
return nil
}
}
}
```
또한 Address 클래스는 String? 반환값을 가지는 buildingIdentifer 이란 이름의 메소드를 제공한다. 이 메소드는 buildingName과 buildingNumber 프로퍼티를 확인해서 만약 buildingName이 값을 가진다면 그 값을 혹은 buildingNumber이 값을 가진다면 그 값을, 둘다 값이 없다면 nil을 반환한다.
## 선택 묶임를 통한 프로퍼티 호출
강제 랩핑 해제(Forced Unwrapping) 대안으로써 선택 묶임에서 봤던 것처럼 선택 묶임를 선택적 값에 대한 프로퍼티 접근에 접근할 수 있는지 만약 프로퍼티 접근이 가능한지 확인하기 위해 사용할 수 있다. 그러나선택 묶임를 통해 프로퍼티의 값을 설정하는 것은 할 수 없다.
위에 정의한 새로운 Person 인스턴스를 사용해 클래스를 만들어 이전처럼 numberOfRooms 프로퍼티에 접근하기를 시도해본다.
```
let john = Person()
if let roomCount = john.residence?.numberOfRooms {
println("John's residence has \(roomCount) room(s).")
} else {
println("Unable to retrieve the number of rooms.")
}
// prints "Unable to retrieve the number of rooms."
```
john.residence가 nil이기 때문에 이 선택 묶임를 예전과 동일한 방식으로 호출했지만 에러 없이 실패한다.
## 선택 묶임를 통한 메소드 호출
선택 묶임를 사용해서 선택적 값을 호출하고 메소드 호출이 성공했는지 여부를 확인해볼 수 있다. 설렁 메소드가 반환값을 정의하지 않더라고 할 수 있다.
Residence 클래스에 있는 printNumberOfRooms 메소드는 numberOfRooms의 현재 값을 출력한다. 그 메소드는 다음과 같을 것이다.
```
func printNumberOfRooms() {
println("The number of rooms is \(numberOfRooms)")
}
```
이 메소드는 반환값을 명시하지 않았다. 그러나 반환형이 없는 함수와 메소드는 Functions Without Return Values에 나와 있는 것처럼 암시적으로 Void 타입을 반환하게 된다.
만약 선택 묶임에 있는 선택지 값에 이 메소드를 호출한다면, 메소드 반환형은 Void가 아니라 Void?이 될 것이다. 선택 묶임를 통해 호출될 때 선택적 타입은 항상 반환 값을 가지기 때문이다. 이는 메소드가 반환값이 정의되어 있지 않더라도 printNumberOfRooms 메소드를 호출이 가능한지를 if문을 써서 확인할 수 있게 한다. printNumberOfRooms에서 암시적 반환값은 만약 메소드가 선택 묶임를 통해 성공적으로 호출되었다면 Void와 동일할 것이고 그렇지 않다면 nil과 동일할 것이다.
```
if john.residence?.printNumberOfRooms() {
println("It was possible to print the number of rooms.")
} else {
println("It was not possible to print the number of rooms.")
}
// prints "It was not possible to print the number of rooms."
```
## 선택 묶임를 통한 서브스크립트 호출
선택적값에 대한 서브스크립트에서 값을 가져와서 서브스크립트 호출이 성공했는지 확인하기 위해 선택 묶임를 사용할 수 있다. 그러나 선택 묶임를 통해 서브스크립트로 값을 설정하는 것은 할 수 없다.
>노트
선택연쇄를 통해 선택적값에 대한 서브스크립트를 접근할 때 서브스크립트 꺽은 괄호(bracket) 앞에 물음표를 놓아야 한다. 뒤가 아니다. 선택연쇄 물음표는 항상 선택적인 표현식의 뒤에 바로 따라나와야 한다.
아래 예는 Residence 클래스에 정의되어 있는 서브스크립트를 사용하는 john.residence 프로퍼티의 rooms 배열에 있는 첫번째 방이름을 집어오려고 하는 것이다. john.residence가 현재 nil이기 때문에 서브스크립트는 실패한다.
```
if let firstRoomName = john.residence?[0].name {
println("The first room name is \(firstRoomName).")
} else {
println("Unable to retrieve the first room name.")
}
// prints "Unable to retrieve the first room name."
```
이 서브스크립트 호출 속에 있는 선택연쇄 물음표는 john.residence 바로 뒤, 서브스크립트 꺽은 괄호 전에 존재해야한다. 왜냐하면, john.residence가 선택연쇄를 꾀할 선택적 값이기 때문이다.
만약, john.residence에 rooms 배열에 한개 이상의 Room 인스턴스도 같이 실제 Residence를 만들어서 할당한다면 선택 묶임를 통해 rooms 배열안의 실제 아이템에 접근하기 위해서 Residence 서브스크립트를 사용할 수 있다.
```
let johnsHouse = Residence()
johnsHouse.rooms += Room(name: "Living Room")
johnsHouse.rooms += Room(name: "Kitchen")
john.residence = johnsHouse
if let firstRoomName = john.residence?[0].name {
println("The first room name is \(firstRoomName).")
} else {
println("Unable to retrieve the first room name.")
}
// prints "The first room name is Living Room."
```
## 다단계 묶임 연결하기
프로퍼티와 메소드, 서브스크립트를 사용해 모델 깊이 파고들기 위해서 선택 묶임를 여러 단계로 함께 엮을 수 있다. 그러나 다단계 선택 묶임로 반환값에 더 많은 선택적임 단계를 넣을 수는 없다.
다른 방식으로:
- 만약 집어오려고 하는 타입이 선택적이지 않으면, 선택 묶임로 인해 선택적으로 변경될 것이다.
- 만약 집어오려고 하는 타입이 이미 선택적이라면, 묶임으로 인해 더 선택적으로 변경되지는 않을 것이다.
그러므로:
- Int 타입을 선택 옵션을 통해 집어오려고 하면, 항상 Int?가 반환될 것이다. 얼마나 많은 단계의 묶임이 사용되었는지는 중요하지 않다.
- 유사하게, Int? 값을 집어오려고 하면, 항상 Int?가 반환될 것이다. 얼마나 많은 단계의 묶임이 사용되었는지는 중요하지 않다.
아래 예는 john의 residence 프로퍼티의 address 프로퍼티의 street 프로퍼티에 접근하려는 것을 보여준다. 여기에 사용되는 2개의 선택적 묶임 단계가 있는데 residence와 address로 둘은 엮여 있고 둘다 선택적 타입이다.
```
if let johnsStreet = john.residence?.address?.street {
println("John's street name is \(johnsStreet).")
} else {
println("Unable to retrieve the address.")
}
// prints "Unable to retrieve the address."
```
john.residence의 값은 현재 적합한 Residence 인스턴스를 포함하고 있다. 그러나 john.residence.address의 값은 현재 nil이다. 이때문에, john.residence?.address?.street 호출은 실패한다.
위 예제를 잘 생각해보자. street 프로퍼티 값을 집어오고자 했다. 이 프로퍼티는 String? 이다. 그러므로 john.residence?.address?.street 의 반환값 역시 두단계 선택 묶임로 프로퍼티가 선택적 타입에 추가로 더해 적용되었음에도 불구하고 String? 이다.
만약 john.residence.address 의 값으로써 실제 Address 인스턴스를 설정하고 그 Adress의 street 프로퍼티에 실제 값을 설정한다면, 다단계 선택 묶임를 통해 그 프로퍼티 값을 접근할 수 있을 것이다.
```
let johnsAddress = Address()
johnsAddress.buildingName = "The Larches"
johnsAddress.street = "Laurel Street"
john.residence!.address = johnsAddress
if let johnsStreet = john.residence?.address?.street {
println("John's street name is \(johnsStreet).")
} else {
println("Unable to retrieve the address.")
}
// prints "John's street name is Laurel Street."
```
john.residence.address 의 address 인스턴스에 할당하기 위해서 느낌표를 사용한 것을 잘보자. john.residence 프로퍼티는 선택적 타입을 가지기에 Residence의 address 프로퍼티에 접근하기 전에 느낌표를 사용해서 그 실제 값을 까볼 필요가 있다.
## 선택적 반환값을 사용해서 메소드 묶임
이전 예제는 선택 묶임를 사용해서 선택적 타입의 프로퍼티의 값을 어떻게 집어오는지 보여주었다. 또한 선택 묶임을 사용해서 선택적 타입 값을 반환하는 메소드를 호출하고 필요하다면 그 메소드의 반환값을 연결할 수 있었다.
아래 예제는 선택 묶임을 통해 Address 클래스의 buildingIndentifer 메소드를 호출한다. 이 메소드는 String? 타입의 값을 반환한다. 이전에 설명한데로, 선택 묶임에 따라 호출된 메소드의 최종 반환값 또한 String?이 된다.
```
if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
println("John's building identifier is \(buildingIdentifier).")
}
// prints "John's building identifier is The Larches."
```
만약 이 메소드 반환값 이상의 선택 묶임을 실행하기 원한다면, 메소드 둥근 괄호(parentheses) 다음에 선택 묶음 물음표를 두면 된다.
```
if let upper = john.residence?.address?.buildingIdentifier()?.uppercaseString {
println("John's uppercase building identifier is \(upper).")
}
// prints "John's uppercase building identifier is THE LARCHES."
```
>노트
위 예제에서 둥근 괄호 다음에 선택 묶음 물음표를 놓았는데, 묶고자 하는 선택적 값이 buildingIndentifer 자체가 아니라 buildingIndentifer 메소드의 반환값이기 때문이다.