-
Notifications
You must be signed in to change notification settings - Fork 28
/
chapter18.txt
409 lines (356 loc) · 36.4 KB
/
chapter18.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
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
# 18 자동 참조 계수 (Automatic Reference Counting)
> Translator : Quartet ( [email protected] )
스위프트는 앱의 메모리 사용량을 추척, 관리하기 위해 자동 참조 계수(ARC)를 사용합니다. 대부분의 경우, 이러한 메모리 관리는 스위프트에서 "그냥 잘 작동합니다". 개발자가 메모리 관리에 대해서 생각할 필요가 없다는 것이죠. ARC는 인스턴스가 더이상 필요가 없을때 해당 클래스 인스턴스가 쓰는 메모리를 자동으로 해제합니다.
하지만 때때로 ARC는 메모리 관리를 하기 위해서 코드 부분들간의 관계 정보를 알아야 할 때가 있습니다. 이번 장은 그러한 상황을 설명하고, 어떻게 ARC가 앱의 메모리 관리를 가능하게 하는지 보여줍니다.
>NOTE
참조 계수는 오직 클래스의 인스턴스에만 적용됩니다. 구조체와 열거형은 값(Value) 타입이며, 참조 타입이 아닙니다. 또한 참조형태로 저장되거나 전달되지 않습니다.
## ARC는 어떻게 작동하는가
클래스의 새 인스턴스를 만들때마다 인스턴스에 대한 정보를 저장하기 위해 ARC는 메모리 덩어리들을 할당합니다. 이 메모리는 인스턴스 타입에 관련된 정보와, 인스턴스와 관련된 저장 속성의 값들을 저장합니다.
그리고 ARC는 더이상 필요하지 않은 인스턴스의 메모리 할당을 해제하여 메모리가 다른 목적으로 이용될 수 있게 합니다. 이로써 클래스 인스턴스가 필요하지 않은 메모리를 차지하고 있는 것을 방지합니다.
하지만 만약 사용중인 인스턴스를 ARC가 할당 해제하면, 해당 인스턴스의 속성에 접근하거나 메소드를 호출하는 것은 불가능해집니다. 사실, 만약 그 인스턴스에 접근하려하면 앱은 크러시(Crash)가 날것입니다.
사용되고 있는 인스턴스가 사라지지 않게 하기 위해 ARC는 얼마나 많은 속성, 상수, 변수들이 각각의 클래스 인스턴스들을 참조하는지 추적합니다. 최소한 하나의 활성화 참조가 있는 이상, ARC는 해당 인스턴스의 할당을 해제하지 않습니다.
이를 가능하게 하기위해, 클래스 인스턴스를 속성, 상수, 변수에 할당할때 해당 속성, 상수, 변수는 해당 인스턴스에 강한 참조(Strong reference)를 합니다. 이 참조는 "강한" 참조라 불리는데, 해당 인스턴스를 강력하게 유지하기 때문입니다. 그리고 이 강한 참조가 남아있는 이상 해당 인스턴스의 할당 해제는 허용되지 않습니다.
## ARC in Action
여기 자동 참조 계수가 어떻게 작동하는지에 대한 예제가 있습니다. 이 예제는 `name` 이라는 저장된 상수 속성을 정의하는 단순한 클래스 `Person`을 보여줍니다.
```
class Person {
let name: String
init(name: String) {
self.name = name
println("\(name) is being initialized")
}
deinit {
println("\(name) is being deinitialized")
}
}
```
`Person` 클래스는 `name` 속성을 설정하고 초기화가 진행중임을 알리는 메시지를 출력하는 이니셜라이져(initializer)를 가지고 있습니다. 또한 `Person` 클래스는 인스턴스가 할당 해제될 때 메시지를 출력하는 디이니셜라이져(deinitializer)를 갖고 있습니다.
다음 코드 조각들은 `Person?` 타입의 변수 3개를 정의하고 있습니다. 이 뒤에 `Person`의 새 인스턴스들의 복수 참조에 사용하기 위한 것입니다. 타입은 `Person`이 아닌 `Person?`인 옵셔널(Optional) 타입이기 때문에, 변수들은 자동적으로 `nil`로 초기화가 되며, 지금은 `Person` 인스턴스를 참조하지 않습니다.
```
var reference1: Person?
var reference2: Person?
var reference3: Person?
```
이제 새로운 `Person` 인스턴스를 생성하여 변수 3개중에 하나에 할당할 수 있습니다.
```
reference1 = Person(name: "John Appleseed")
// prints "John Appleseed is being initialized"
```
`"John Appleseed is being initialized"`라는 메시지가 `Person` 클래스의 이니셜라이져가 호출될 때 출력된다는 점에 주의합니다. 이것으로 초기화가 제대로 됐음을 확인할 수 있습니다.
`reference1` 변수에 `Person`의 새 인스턴스가 할당 되었기 때문에, `reference1`과 `Person` 인스턴스 사이에 강한 참조가 생깁니다. 그리고 최소한 하나의 강한 참조가 있어서 ARC는 `Person`이 메모리에 유지되는 것과, 할당 해제 되지 않음을 확인 합니다.
만약 같은 `Person` 인스턴스를 두개 변수에 더 할당하면, 두개의 강한 참조가 더 생깁니다.
```
reference2 = reference1
reference3 = reference1
```
이제 하나의 `Person` 인스턴스에 대한 강한 참조는 3개입니다.
원래의 참조를 포함한 변수들 중에 `nil`을 2개 할당함으로써 2개의 강한 참조를 부순다면, 하나의 강한 참조가 남게 되며, 여전히 `Person` 인스턴스는 할당해제 되지 않습니다.
```
reference1 = nil
reference2 = nil
```
세번째 강한 참조가 사라져 명확하게 `Person` 인스턴스가 더 이상 사용되지 않기 전까지 ARC는 `Person` 인스턴스를 할당 해제 하지 않습니다.
```
reference3 = nil
// prints "John Appleseed is being deinitialized"
```
## 클래스 인스턴스간의 강한 참조 순환
위 예제에서 ARC는 생성된 `Person` 인스턴스의 참조 갯수를 추적하고 해당 `Person` 인스턴스가 더이상 필요하지 않을때 할당 해제를 합니다.
하지만 _절대로_ 강한 참조의 갯수가 0으로 떨어지지 않게 코드를 작성하는 것이 가능합니다. 두개의 클래스 인스턴스가 서로를 강하게 잡고 있을때 그 현상이 발생합니다. 인스턴스 서로가 서로를 살게끔 유지하는 것이죠. 이를 _강한 참조 순환(strong referecne cycle)_이라고 합니다.
강한 참조 순환을 풀려면 클래스간의 관계를 강한 참조 대신 약한(weak) 참조나 미소유 참조(unowned references)로 대체해야 합니다. 이 과정은 __Resolving Strong Reference Cycles __에 설명이 되어있습니다. 하지만 강한 참조 순환을 푸는걸 배우기 전에, 어떻게 순환이 생기는지 이해하는것이 좋습니다.
이 예제는 강한 참조 순환이 어떻게 의도치 않게 생기는지 보여줍니다. 이 예제는 아파트 블록과 거기에 사는 사람을 모델링하는 `Person`과 `Apartment` 두개의 클래스를 정의합니다.
```
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { println("\(name) is being deinitialized") }
}
class Apartment {
let number: Int
init(number: Int) { self.number = number }
var tenant: Person?
deinit { println("Apartment #\(number) is being deinitialized") }
}
```
모든 `Person` 인스턴스는 `String`타입의 `name` 속성을 가지고 있고, 추가적으로 `apartment` 속성을 최초에 `nil` 값이 할당된 채로 가집니다. `apartment` 속성은 옵셔널입니다. 어떤 사람은 아파트에 살지 않을 수도 있기 때문입니다.
비슷하게, 모든 `Apartment` 인스턴스는 `Int` 타입의 `number` 속성을 가지고 있고, 추가적으로 최초에 `nil`이 할당된 `tenant` 속성을 가지고 있습니다. `tenant` 속성은 옵셔널입니다. 어떤 아파트는 사람이 살지 않을 수도 있기 때문입니다.
두 클래스 전부 디이니셜라이저를 정의하여 클래스 인스턴스가 디이니셜라이(역주: 혹은 할당 해제) 된다는 사실을 출력하고있습니다. 이로 인해 `Person`과 `Apartment` 인스턴스가 기대한대로 할당 해제가 되는걸 볼 수 있습니다.
다음 코드 조각은 `john`과 `number73`이라는 변수를 정의하고 있습니다. 이 변수들에 밑의 `Apartment`와 `Person` 인스턴스를 설정할겁니다. 두 변수는 옵셔널이기에 초기값으로 `nil`을 가집니다.
```
var john: Person?
var number73: Apartment?
```
이제 `Person`과 `Apartment`의 인스턴스를 생성해서 `john`과 `number73` 변수에 할당 할 수 있습니다.
```
john = Person(name: "John Appleseed")
number73 = Apartment(number: 73)
```
두 인스턴스를 생성 후에 할당하여 강한 참조가 어떻게 구성되는지 보여주는 그림입니다. `john` 변수는 새 `Person` 인스턴스에 강한 참조를 가지고 있으며 `number73` 변수는 `Apartment` 인스턴스에 강한 참조를 가지고 있습니다.
![referencecycle01_2x.png](https://raw.githubusercontent.com/lean-tra/Swift-Korean/master/images/referencecycle01_2x.png)
이제 두 인스턴스를 서로 연결하여 사람(person)이 아파트를 가지고, 아파트가 사람을 가지게 할 수 있습니다. 여기서 느낌표(`!`)는 `john`과 `number73` 인스턴스 안에 저장된 옵셔널(optional) 변수를 드러내어 접근할 수 있게 하는 것입니다. 그렇게 인스턴스의 속성은 다음과 같이 설정 될 수 있습니다.
```
john!.apartment = number73
number73!.tenant = john
```
여기 그림은 두 인스턴스간에 강한 참조가 어떻게 형성되어있는지를 보여줍니다.
![referencecycle02_2x.png](https://raw.githubusercontent.com/lean-tra/Swift-Korean/master/images/referencecycle02_2x.png)
안타깝게도 이러한 두 인스턴스간의 연결은 서로간의 강한 참조 순환을 발생시킵니다. `Person` 인스턴스는 `Apartment` 인스턴스에 대한 강한 참조를 가지고 있고, `Apartment` 인스턴스는 `Person` 인스턴스에 대한 강한 참조를 가지게 됩니다. 그러므로 `john`과 `number73` 변수만을 이용하여 강한 참조를 없애려할때, 참조 계수는 0으로 떨어지지 않으며 ARC에 의해 인스턴스가 할당해제 되지 않습니다.
```
john = nil
number73 = nil
```
두 변수가 `nil`로 할당 될 때 디이니셜라이저가 호출되지 않았음에 주의하세요. 강한 참조 순환은 `Person`과 `Apartment`의 인스턴스가 영원히 할당 해제 되지 않게하여 앱의 메모리 누수(leak)가 일어나게 합니다.
이 그림은 `john`과 `number73` 변수가 `nil` 로 할당 된 후의 강한 참조가 어떻게 되었는지 보여줍니다.
![referencecycle03_2x.png](https://raw.githubusercontent.com/lean-tra/Swift-Korean/master/images/referencecycle03_2x.png)
`Person`과 `Apartment`간의 강한 참조는 여전히 남아있으며, 깨어질 수 없게 되었습니다.
## 클래스 인스턴스간의 강한 참조 순환 해결하기
스위프트는 약한 참조와 미소유 참조라는 2가지 방법 제공하여 클래스 속성에서 일어나는 강한 참조 순환을 해결할 수 있게합니다.
약한 참조나 미소유 참조는 참조 순환의 안에 있는 인스턴스가 다른 인스턴스에 대해 강한 참조를 유지할 필요 없이 참조할 수 있게 합니다. 인스턴스는 서로를 강한 참조 없이 참조 할 수 있게 됩니다.
약한 참조는 해당 참조가 살아있는 동안 잠시라도 `nil`이 될때 사용하게 됩니다. 그와 반대로 미소유 참조는 참조가 초기화 과정 중 설정 되고 이후에 절대로 `nil`이 되지 않음을 알고 있을 때 사용합니다.
### 약한 참조
_약한 참조_는 인스턴스가 다른 인스턴스를 참조하는데 강하게 유지하지 않는 참조이며, 그렇기에 ARC가 참조된 인스턴스를 버리는 것을 멈추게 하지 않습니다. 이로 인해 참조가 강한 참조 순환의 일부가 되는 것을 방지합니다. `weak` 키워드를 선언의 앞에 위치시키는 것으로 속성이나 변수 선언이 약한 참조라고 알릴 수 있습니다.
약한 참조는 참조가 어느 순간 "값 없음"을 참조하게 될때 사용되어 참조 순환을 피하는데 이용 됩니다. 만약 참조가 언제나 값을 가진다면 __미소유 참조__에 설명된 것처럼 미소유 참조를 대신 사용하면 됩니다. 위의 `Apartment` 예제에서는 아파트가 "거주자 없음" 상태를 가지는 것이 자연스럽기에 약한 참조를 사용하여 참조 순환을 부술 수 있습니다.
>NOTE
약한 참조는 실행 시간중에 값이 바뀔 수 있기 때문에 반드시 변수로서 선언되어야 합니다. 약한 참조는 상수로 선언될 수 없습니다.
약한 참조는 "값 없음"을 가지는게 허용되기에, 약한 참조는 언제나 옵셔널 타입으로 선언되어야 합니다. 옵셔널 타입은 스위프트에서 "값 없음"을 표현하는데 선호되는 방식입니다.
약한 참조는 인스턴스를 강하게 참조 하고 있지 않기 때문에 약한 참조를 통해 참조를 하고 있는 동안 할당 해제가 될 가능성이 있습니다. 때문에 ARC는 약한 참조가 참조하고 있던 인스턴스가 할당 해제 되면 참조를 자동으로 `nil`로 설정합니다. 다른 옵셔널 값들처럼, 약한 참조의 값이 존재하는지를 체크할 수 있습니다. 그렇기 때문에 존재하지 않는 잘못된 인스턴스를 참조하는 일은 일어나지 않습니다. (역주: 아예 `nil`을 참조하는 것과, 있어야 할 자리에 엉뚱한게 있는 것을 참조 하는 것이 다르기에 위의 문장이 나온듯 싶습니다. C에서 포인터를 이용해 강제로 다른 부분을 읽는 것을 생각하면 될것 같습니다.)
밑의 예제는 위의 예제와 똑같지만 중요한 한가지가 다른 `Person`과 `Apartment`입니다. 이번에는 `Apartment` 타입의 `tenant` 속성이 약한 참조로 선언되어 있습니다.
```
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { println("\(name) is being deinitialized") }
}
class Apartment {
let number: Int
init(number: Int) { self.number = number }
weak var tenant: Person?
deinit { println("Apartment #\(number) is being deinitialized") }
}
```
`john`과 `number73` 두 변수의 강한 참조와 두 인스턴스간의 연결은 이전엔 다음과 같았습니다.
```
var john: Person?
var number73: Apartment?
john = Person(name: "John Appleseed")
number73 = Apartment(number: 73)
john!.apartment = number73
number73!.tenant = john
```
이 그림은 두 인스턴스의 현재 참조가 어떤지를 보여줍니다.
![weakreference01_2x.png](https://raw.githubusercontent.com/lean-tra/Swift-Korean/master/images/weakreference01_2x.png)
`Person` 인스턴스는 여전히 `Apartment` 인스턴스를 강한 참조로 하고 있습니다. 하지만 `Apartment` 인스턴스는 이제 `Person`에 대해 _약한 참조_를 하고 있습니다. 이는 곧 `john` 변수에 대한 강한 참조를 없앴을때, `Person`인스턴스에 대한 강한 참조가 없다는 것을 뜻합니다.
![weakreference02_2x.png](https://raw.githubusercontent.com/lean-tra/Swift-Korean/master/images/weakreference02_2x.png)
`Person` 인스턴스에 대한 강한 참조가 더이상 없기에 인스턴스는 할당해제 됩니다.
```
john = nil
// prints "John Appleseed is being deinitialized"
```
`Apartment` 인스턴스에 대한 강한 참조는 `number73` 변수에 대한 것밖에 남지 않았습니다. 그 강한 참조를 사라지게 한다면 `Apartment`에 대한 강한 참조는 더이상 남아있지 않게 됩니다.
![weakreference03_2x.png](https://raw.githubusercontent.com/lean-tra/Swift-Korean/master/images/weakreference03_2x.png)
`Apartment`에 대한 강한 참조가 더이상 없기 때문에, 이 인스턴스 역시 할당 해제 됩니다.
```
number73 = nil
// prints "Apartment #73 is being deinitialized"
```
위 두 코드 조각은 `Person`과 `Apartment`의 디이니셜라이저가 `john`과 `number73` 변수가 `nil`로 설정 될때 "디이니셜라이즈" 메시지를 출력하는 것을 보여줍니다. 이것으로 강한 참조가 사라졌을음 증명할 수 있습니다.
### 미소유 참조
약한 참조처럼 _미소유 참조_ 또한 인스턴스에 대한 참조를 강하게 하지 않습니다. 약한 참조와는 다르게, 미소유 참조는 언제나 값을 가지고 있다고 간주합니다. 이 때문에 미소유 참조는 옵셔널 타입이 아닙니다(non-optional). 미소유 참조는 `unowned` 키워드를 속성이나 변수 선언 앞에 위치 시킴으로써 할 수 있습니다.
미소유 참조는 옵셔널이 아니기 때문에 미소유 참조를 쓸 때마다 드러내야 할 필요가 없습니다. 미소유 참조는 언제나 직접 접근이 가능합니다. 하지만 ARC가 인스턴스의 참조를 할당 해제 할 때 `nil`로 설정 할 수는 없습니다. 옵셔널이 아닌 타입은 `nil`로 설정 될 수 없기 때문입니다.
>NOTE
만약 미소유 참조가 참조하는 인스턴스가 할당 해제된 후에 접근하려 한다면 런타임 에러를 발생 시킬것입니다. 미소유 참조는 언제나 인스턴스를 참조하는 게 확실할 때에만 사용해야 합니다.
스위프트는 미소유 참조가 할당 해제된 인스턴스에 접근하려 할때 언제나 크래시를 낸다는 것에 주의하십시오. 앱은 언제나 안정적으로 크래시할 것입니다. 물론, 당연히 그런 일이 일어나지 않게 해야할테지만 말이죠.
다음의 예제는 `Customer`와 `CreditCard` 두 클래스를 정의하고 있습니다. 이 클래스는 은행 고객과 그 고객에게 가능한 신용카드를 모델링합니다. 이 두 클래스는 서로의 인스턴스를 속성으로 저장합니다. 이 관계는 강한 참조 순환을 만들 가능성이 있습니다.
`Customer`와 `CreditCard` 의 관계는 위의 약한 참조 예제에서 살펴본 `Person`과 `Apartment`의 관계와는 조금 다릅니다. 이 데이터 모델에서 고객은 신용 카드를 가질수도 있고 안가질수도 있습니다. 하지만 신용 카드는 _언제나_ 고객과 연관이 됩니다. 그것을 표현하기 위해 `Customer` 클래스는 `card` 속성을 옵셔널 로 가지지만, `CredicCard` 클래스는 `customer` 를 논옵셔널(non-optional) 속성으로 가집니다.
게다가 새로운 `CreditCard` 인스턴스는 오직 `number`값과 `customer` 인스턴스를 `CreditCard`의 맞춤(custom) 이니셜라이저를 통해서만 생성될 수 있습니다. 이를 통해 `CreditCard` 인스턴스가 생성될 때는 언제나 `credit` 인스턴스와 연관이 됨을 보증할 수 있습니다.
신용카드는 언제나 고객을 가지기 때문에 `customer` 속성을 미소유 참조로 설정하여 강한 참조 순환을 피할 수 있습니다.
```
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit { println("\(name) is being deinitialized") }
}
class CreditCard {
let number: Int
unowned let customer: Customer
init(number: Int, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { println("Card #\(number) is being deinitialized") }
}
```
다음 코드 조각은 옵셔널 `Customer` 변수인 `john`을 정의하여 특정한 고객의 정보를 참조하게 하였습니다. 이 변수는 옵셔널 변수임으로 `nil`을 초기값으로 갖습니다.
```
var john: Customer?
```
이제 `Customer` 인스턴스를 생성하여 인스턴스의 `card` 속성에 할당할 `CreditCard` 인스터스의 초기화에 이용할 수 있습다.
```
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
```
이 그림은 위 두 인스턴스간의 관계가 어떻게 되는지 보여주고 있습니다.
![unownedreference01_2x.png](https://raw.githubusercontent.com/lean-tra/Swift-Korean/master/images/unownedreference01_2x.png)
`Customer` 인스턴스는 `CreditCard`에 대해 강한 참조를 하고 있습니다. `CreditCard`는 `Customer` 인스턴스에 대해 미소유 참조를 하고 있습니다.
`customer`가 미소유 참조이기 때문에 `john` 변수에 한 강한 참조를 사라지게 한 순간, `Customer`에 대한 강한 참조는 더이상 존재않게 됩니다.
![unownedreference02_2x.png](https://raw.githubusercontent.com/lean-tra/Swift-Korean/master/images/unownedreference02_2x.png)
`Customer` 인스턴스에 대한 강한 참조가 더이상 존재하지 않게되어 인스턴스는 할당 해제 됩니다. 이 일이 일어난 뒤에, `CreditCard` 인스턴스에 대한 강한 참조 역시 더이상 존재하지 않기에 이 또한 할당 해제 됩니다.
```
john = nil
// prints "John Appleseed is being deinitialized"
// prints "Card #1234567890123456 is being deinitialized"
```
위에 있는 마지막 코드 조각은 `john`변수가 `nil`로 설정 된 후 `Customer` 인스턴스와 `CreditCard` 인스턴스가 둘 다 "디이니셜라이즈" 메시지를 출력하는 것을 보여주고 있습니다.
### 미소유 참조와 암시적으로(implicitly) 드러난(unwrapped) 옵셔널 속성
위의 약한 참조와 미소유 참조에 대한 예제는 일반적으로 강한 참조 순환을 부술 필요가 있는 시나리오중 2개를 보여주고 있습니다.
`Person`과 `Apartment` 예제는 두 쪽의 속성이 `nil`이 될 수도 있는 상황에서 강한 참조 순환의 가능성이 있는 상황 이었습니다. 이 시나리오는 약한 참조로 훌륭하게 해결 됩니다.
`Customer`와 `CreditCard` 예제는 한 쪽의 속성이 `nil`이 될 수 있고, 다른 쪽 속성은 `nil`이 되지 않을때 강한 참조 순환이 생길 수 있는 상황 이었습니다. 이 시나리오는 미소유 참조로 훌륭하게 해결됩니다.
하지만 여기에 세번째 시나리오가 있습니다. _양 쪽_의 속성이 모두 언제나 값을 가져야 하며, 속성은 초기화 완료 이후에 `nil` 이 되면 안되는 시나리오입니다. 이 시나리오에서는 한쪽 클래스의 미소유 속성과 다른 쪽 클래스의 암시적으로 드러난 옵셔널 속성이 유용합니다.
이는 양쪽의 속성이 초기화가 한번 완료된 이후에 옵셔널 속성을 드러낼 필요 없이 직접 접근이 될 수 있게 하며, 참조 순환이 일어나지 않게 합니다. 이번 섹션(section)은 이런 관계를 어떻게 설정하는지 보일 것입니다.
밑의 예제는 두개의 클래스 `Country`와 `City`를 정의합니다. 각각의 클래스는 서로의 클래스 인스턴스를 속성으로 저장합니다. 이 데이터 모델에서, 모든 나라들은 언제나 수도를 가지며, 모든 도시는 반드시 나라에 소속되어야합니다. 이를 표현하기 위해서 `Country`는 `capitalCity`속성을 가지고, `City` 클래스는 `country` 속성을 가집니다.
```
class Country {
let name: String
let capitalCity: City!
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
}
```
양 클래스 간의 상호 의존성을 설정하기 위해 `City` 이니셜라이저는 `Country` 인스턴스를 입력받고, 이 인스턴스를 `country` 속성에 저장합니다.
`City`의 이니셜라이저는 `Country` 이니셜라이저 안에서 호출됩니다. 하지만 `Country` 이니셜라이저는 새 `Country` 인스턴스가 완전하게 이니셜라이즈 되기 전까지 `self`를 ` City`로 넘길 수 없습니다. **이 단계 초기화** 에서 설명된 것처럼 말이죠.
이 요구에 대처하기 위해 `Country`의 `capitalCity` 속성을 암시적으로 드러난 옵셔널 속성으로 선언합니다. 그러기 위해서 타입 표시의 끝에 느낌표를 붙이면 됩니다(`City!`). 이는 `capitalCity`가 다른 옵셔널 값들처럼 `nil`을 기본값으로 가짐을 뜻하지만, **암시적으로 드러난 옵셔널**에서 설명한 것과 같이 접근하는데 드러내야할 필요가 없습니다.
`capitalCity`가 기본값으로 `nil`을 가지기에, 새 `Country` 인스턴스는 `Country` 인스턴스의 `name` 속성이 이니셜라이저 안에서 설정 되었을때를 완전히 완전히 초기화 된 순간이라고 간주합니다. 이는 `Country` 이니셜라이저가 암시적으로 `name`속성이 설정 되는 순간부터 `self` 속성을 참조하고 넘겨줄 수 있다는 것을 뜻합니다. 그렇기에 `Country`의 이니셜라이저는 자신의 `capitalCity` 속성을 설정할때 `self`를 `City` 이니셜라이저의 파라메터로 넘겨줄 수 있습니다.
이 모든 것은 강한 참조 순환을 만들지 않고 `Country`와 `City` 인스턴스를 한 문장(statement)안에서 만들 수 있다는 것을 뜻합니다. 그리고 느낌표를 통해 옵셔널 값을 드러내지 않고 `capitalCity` 속성에 직접 접근 할 수 있습니다.
```
var country = Country(name: "Canada", capitalName: "Ottawa")
println("\(country.name)'s capital city is called \(country.capitalCity.name)")
// prints "Canada's capital city is called Ottawa"
```
위의 예제에서는 암시적으로 드러난 옵셔널은 모든 두 단계의 클래스 이니셜라이저 요구사항이 모두 충족되었다는 것을 뜻합니다. `capitalCity` 속성에는 초기화 단계가 일단 끝나고 난후에는 옵셔널이 아닌 값처럼 접근이 가능합니다. 여전히 강한 참조 순환을 만들지 않으면서 말이죠.
## 클로저를 위한 강한 참조 순환
위에서 어떻게 두 클래스 인스턴스의 속성들이 서로 강한 참조를 하면서 강한 참조 순환을 만드는지 보았습니다. 또한 약한 참조와 미소유 참조를 이용해 어떻게 강한 참조 순환을 부수는지도 보았습니다.
클로저를 클래스 인스턴스의 속성에 할당할때도 강한 참조 순환이 발생할 수 있습니다. 해당 클로저의 몸체는 인스턴스를 획득(capture)합니다. 이 획득은 클로저의 몸체가 `self.someProperty`와 같은인스턴스의 속성에 접근하려 할 때 발생합니다. 혹은 클로저가 `self.someMethod()`와 같은 인스턴스의 메소드를 호출 할 때도 발생합니다. 어느 경우든간에 그러한 접근에서 클로저는 `self`를 획득하게 되며, 강한 참조 순환을 만들어냅니다.
이 강한 참조 순환은 클로저가 클래스와 같이 _참조 타입_ 이기 떄문에 일어납니다. 클로저를 속성에 할당하면, _참조_를 클로저에 할당하는 것이 됩니다. 본질적으로, 이는 위에서 말한 문제와 같은 문제입니다. 두개의 강한 참조가 서로를 살아있게 만듭니다. 하지만 이번엔 두개의 클래스 인스턴스가 아니라, 하나의 클래스 인스턴스와 클로저가 서로를 살아있게 합니다.
스위프트는 이 문제에 대해 _클로저 획득 목록_이라는 우아한 방법을 제공합니다. 하지만 클로저 획득 목록을 이용하여 강한 참조 순환을 부수는 방법을 배우기 전에, 어떻게 순환이 야기되는지 이해하는 것이 좋습니다.
밑의 예제는 `self`를 클로저가 참조하면서 어떻게 강한 참조 순환이 생겨나는지 보여줍니다. 이 예제는`HTMLElement` 클래스를 정의해서 HTML 문서와 그 안에 포함된 개개의 요소를 모델링하고 있습니다.
```
class HTMLElement {
let name: String
let text: String?
@lazy var asHTML: () -> String = {
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
println("\(name) is being deinitialized")
}
}
```
`HTMLElement` 클래스는 `name` 속성을 정의하여 요소의 이름을 가리키고 있습니다. 문단 요소인 `"p"`나 줄바꿈 요소인 `"br"`등. `HTMLElement`는 또한 `text` 속성을 정의하여 HTML 속성내에서 텍스트가 설정되어서 보일 수 있게 합니다.
그런 간단한 두 속성 외에 `HTMLElement`는 `asHTML`이라는 느린(lazy) 속성을 정의합니다. 이 속성은 HTML 문자열 조각 안에 있는 `name`과 `text` 조합된것을 참조합니다. `asHTML` 속성의 타입은 `() -> String`이며, 다른 말로는 " 파라메터를 받지않고, `String` 값을 반환하는 함수" 라 할 수 있습니다.
기본적으로 `asHTML` 속성은 HTML태그의 문자열 표현을 반환하는 클로저에 할당되어있습니다. 이 태그는 옵셔널인 `text`값이 존재할 경우 그것을 포함하게 되며, `text`가 존재하지 않을때는 아무런 텍스트 내용을 가지지 않습니다. 문단 요소에 대해 이 클로저는 `text` 속성이 `"some text"`나 `nil`중 어느것에 해당하는지에 따라서 `<p>some text</p>`를 반환하거나 `<p />`를 반환합니다.
이 `asHTML`은 인스턴스 메소드와 비슷한 것처럼 이름 지어지고, 사용됩니다. 하지만 `asHTML`은 인스턴스 메소드가 아닌 클로저 속성이기에, 특정 HTML 요소에 대해 HTML 렌더링을 바꾸고 싶다면 기본값을 대체하여 맞춤(custom) 클로저로 바꿀 수 있습니다.
>NOTE
이 `asHTML` 속성은 느린(lazy) 속성으로 선언되어 있습니다. 특정 HTML 출력 목표에 대해 문자열 값을 렌더링해야할 필요가 있을때만 필요해지기 때문입니다. `asHTML`이 느림 속성이기 때문에 `self`를 기본 클로저 안에서 참조할 수 있습니다. 느린 속성은 초기화가 완료 되어 `self`가 존재하기 전까지는 접근이 되지 않기 때문입니다.
`HTMLElement` 클래스는 하나의 이니셜라이저를 제공하여 `name` 인자와 필요하다면 `text` 인자를 받아 새 요소를 초기화합니다. 또한 이 클래스는 디이니셜라이저를 정의하여 `HTMLElement`가 할당 해제 될 때 메시지를 출력하게 합니다.
여기 `HTMLElement` 클래스를 생성하여 새 인스턴스가 어떻게 출력을 하는지 예제가 있습니다.
```
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
println(paragraph!.asHTML())
// prints "<p>hello, world</p>"
```
>NOTE
위의 `paragraph` 변수는 옵셔널 `HTMLElement`로 정의되어있습니다. 그래서 아래에서 `nil`로 설정되면 강한 참조 순환이 존재하게 됨을 보일 수 있습니다.
안타깝게도, 위에 쓰여진대로 `HTMLElement` 클래스는 `HTMLElement` 인스턴스와 `asHTML`의 기본값으로 설정된 클로저 사이에 강한 참조 순환을 만들게 되었습니다. 그림은 그 순환이 어떻게 생겼는지 보여줍니다.
![closurereferencecycle01_2x.png](https://raw.githubusercontent.com/lean-tra/Swift-Korean/master/images/closurereferencecycle01_2x.png)
인스턴스의 `asHTML` 속성은 해당 클로저에 대해 강한 참조를 하고 있습니다. 하지만 클로저가 그 몸체 안에서 `self.name`과 `self.text`를 참조하는 방법으로 `self`를 참조하고 있기에 클로저는 인스턴스 자신을(slef) _획득_하게 됩니다. 즉 `HTMLElement` 인스턴스를 참조하게 되어 강한 참조를 하게 됩니다. 이렇게 둘 사이에 강한 참조 순환이 형성되게 됩니다. (클로저의 값 획득에 대해서 더 자세한 정보는 [값 획득](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-XID_129)을 보세요)
>NOTE
클로저가 `self`를 여러번 참조한다고 해도, `HTMLElement`에 대한 강한 참조는 오직 한번만 획득하게 됩니다.
만약 `paragraph` 변수를 `nil`로 설정하고, 이 `HTMLElement`에 대한 강한 참조를 부순다면, `HTMLElement` 인스턴스나 그 클로저는 할당 해제되지 않습니다. 강한 참조 순환이 있기 때문입니다.
```
paragraph = nil
```
`HTMLElement`의 디이니셜라이저가 아무런 메시지도 출력하지 않았음에 주의하세요. 이는 곧 `HTMLElement` 인스턴스가 할당 해제 되지 않았음을 의미합니다.
## 클로저의 강한 참조 순환 해결하기.
클로저와 클래스의 강한 참조 순환은 클로저 정의의 일부로서 _획득 목록_을 정의하는 것으로 해결 할 수 있습니다. 획득 목록은 하나 이상의 참조 타입이 클로저의 몸체에 있을때 사용할 규칙을 정의합니다. 두 클래스 인스턴스 간의 강한 참조 순환 처럼, 획득한 참조를 강한 참조대신 약한 참조나 미소유 참조로 선언할 수 있습니다. 약한 참조나 미소유 참조중 어느 것이 더 적절한지는 코드의 다른 부분에 따라 다릅니다.
>NOTE
스위프트는 `self`의 멤버를 클로저 안에서 참조할때 `someProperty`나 `someMethod` 대신에 `self.someProperty`나 `self.someMethod`로 표기할 것을 요구합니다. 이렇게 함으로써 `self`가 의도치 않게 획득 될 수 있음을 기억하는데 도움이 됩니다.
### 획득 목록 정의하기
획득 목록의 각각의 아이템은 `self`나 `someInstance`같은 클래스 인스턴스와 참조간의 약한 참조 또는 미소유 참조의 쌍입니다. 각 쌍들은 대괄호안에 쓰여지며, 콤마로 구분됩니다.
획득 목록은 클로저에 파라메터 목록이나 반환 타입이 있다면 그 앞에 위치시킵니다.
```
@lazy var someClosure: (Int, String) -> String = {
[unowned self] (index: Int, stringToProcess: String) -> String in
// closure body goes here
}
```
만약 클로저의 파라메터 목록이나 반환 타입이 문맥에서 암시되어 특별히 정해지지 않았다면, 획득 목록은 클로저의 시작 부분인 `in` 바로 앞에 둡니다.
```
@lazy var someClosure: () -> String = {
[unowned self] in
// closure body goes here
}
```
### 약한 참조와 미소유 참조
클로저와 클로저가 획득한 인스턴스가 언제나 서로를 참조할때, 획득을 미소유 참조로 정의합니다. 그렇게 되면 같은 시점에 서로 할당 해제가 될것입니다.
그와 반대로, 클로저가 획득한 참조가 언젠가는 `nil`이 될때, 획득을 약한 참조로 정의합니다. 약한 참조는 언제나 옵셔널 타입이며, 참조중인 인스턴스가 할당 해제가 되면 자동적으로 `nil`이 됩니다. 이로 인해 클로저 몸체에서 인스턴스의 존재를 확인할 수 있습니다.
>NOTE
만약 획득된 참조가 절대로 `nil`이 되지 않는다면, 그 참조는 약한 참조보다 미소유 참조로 해야할것입니다.
미소유 참조는 위의 `HTMLElement` 예제에서 강한 참조 순환을 풀기에 적절한 획득 방법입니다. 다음은 `HTMLElement` 클래스가 순환을 어떻게 피해야 할지 보여줍니다.
```
class HTMLElement {
let name: String
let text: String?
@lazy var asHTML: () -> String = {
[unowned self] in
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
println("\(name) is being deinitialized")
}
}
```
위의 `HTMLElement` 구현은 이전의 구현과 동일합니다. `asHTML` 클로저의 획득 목록 부분을 제외하면 말이죠. 이 경우에 획득 목록은 `[unowned self],`며, 이는 "인스턴스를 강한 참조가 아닌 미소유 참조로서 획득한다"라 할 수 있습니다.
이제 이전처럼 `HTMLElement` 인스턴스를 생성하여 출력할 수 있습니다.
```
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
println(paragraph!.asHTML())
// prints "<p>hello, world</p>"
```
이 그림은 획득 목록을 사용한 참조들이 어떻게 보여줍니다.
![closurereferencecycle02_2x.png](https://raw.githubusercontent.com/lean-tra/Swift-Korean/master/images/closurereferencecycle02_2x.png)
이번의 클로저에 의한 `self` 획득은 미소유 참조입니다. 그렇기에 획득한 `HTMLElement` 인스턴스를 강하게 유지하지 않습니다. 만약 `paragraph` 변수의 강한 참조를 `nil`로 설정한다면, `HTMLElement` 인스턴스는 할당 해제가 될 것입니다. 밑의 예제에서 보이는 것처럼 디이니셜라이저 메시지를 출력하면서 말이죠.
```
paragraph = nil
// prints "p is being deinitialized"
```