티스토리 뷰
안녕하세요 Hani입니다.
지난번 클래스의 강한 참조 순환 포스팅 에서는 weak unowned 키워드를 통해 문제를 해결했죠?
이번 주제는 대망의 클로저의 강한 참조 순환입니다.
제가 최근 ARC에 대해 공부를 시작하게 된 계기..! [weak self] 를 쓰는 이유를 오늘 알게 될 거예요. 👍
그럼 코드를 한번 봐봅시당
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 {
print("\(name) is being deinitialized")
}
}
이 클래스의 asHTML는 전달 인자를 받지 않고 출력 값이 String인 () -> String 클로저를 사용하고 있습니다.
클로저 내부에서는 HTMLElement의 name과 text를 얻어내기 위해 self를 캡쳐하고 있네요.
그럼 클래스의 인스턴스를 생성해보겠습니다.
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// Prints "<p>hello, world</p>"
paragraph 변수는 HTMLElement(name: "p", text: "hello, world") 인스턴스를 참조하고 있죠?
paragraph 변수가 참조하는 인스턴스의 asHTML는 () -> String 클로저를 참조하고 있고,
그림으로 나타내면 아래와 같은 모습입니다.
여기서 paragraph 변수의 인스턴스 참조를 끊어버리면 어떻게 될까요?
paragraph = nil
paragraph 변수가 인스턴스를 참조하는 것을 끊어도 인스턴스가 메모리에서 사라지지 않습니다.
왜냐면 인스턴스와 클로저간 강한 순환 참조가 발생하기 때문이에요.
소멸자 역시 실행되지 않은 모습을 볼 수 있습니다.
클로저의 강한 순환 참조 문제의 해결 방안은 캡쳐 리스트(Capture List)입니다.
캡쳐 리스트는 캡쳐할 대상 앞에 참조 타입을 적어서 정의할 수 있어요.
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 {
print("\(name) is being deinitialized")
}
}
클로저의 참조 타입은 weak, unowned가 있어요. (디폴트는 역시 strong)
아까랑 달리 [unowned self] 가 들어갔죠?
self, 그러니까 HTMLElement클래스의 인스턴스를 앞으로는 미소유 참조하겠다는 얘기입니다.
이대로 인스턴스를 생성해볼게요.
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// Prints "<p>hello, world</p>"
약한 참조와 미소유 참조는 참조 횟수를 증가시키지 않죠?
인스턴스와 클로저의 참조 횟수는 각각 1인 상태입니다.
이제 paragraph 변수의 인스턴스 참조를 끊어보겠습니다.
paragraph = nil
// Prints "p is being deinitialized"
HTMLElement 인스턴스는 참조 횟수가 0으로 되니까 인스턴스의 클로저 참조도 끊어지게 됩니다.
결국 참조 횟수가 0이 된 클로저도 인스턴스와 같은 타이밍에 메모리에서 사라지게 되죠.
이 경우 클로저와 인스턴스의 수명이 같기 때문에 unowned를 사용해도 무방해 보입니다.
그럼 weak self는?
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = {
[weak 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 {
print("\(name) is being deinitialized")
}
}
weak은 참조할 대상이 nil일 가능성을 포함하니까 참조 대상에 옵셔널을 붙여줍니다.
클로저에서 왜 weak self를 쓰는지 알기 위해 많은 시간이 걸렸지만 (무려 포스팅 3개.. 🔥)
건진 게 많아서 기분은 좋네요 ☺️
References
https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html
'Swift' 카테고리의 다른 글
[스위프트] Collection 프로토콜 (0) | 2021.08.28 |
---|---|
[스위프트] lazy 지연 저장 프로퍼티 (3) | 2021.08.17 |
[Swift] 클래스 강한 순환 참조 (0) | 2021.05.17 |
[Swift] ARC (Automatic Reference Counting) 정리 (0) | 2021.05.17 |
[Swift] 일급 객체 First class Citizen (0) | 2021.05.15 |
- Total
- Today
- Yesterday
- rxswift
- 최대 매칭
- CompositionalLayout
- WWDC16
- State Restoration
- CPU와 Memory
- observeOn
- 다익스트라 시간복잡도
- 포드 풀커슨 알고리즘
- 코딩대회
- 에드몬드 카프 알고리즘
- Testable
- 벨만포드 알고리즘
- mach-o
- 벨만포드 시간복잡도
- 컴퓨터 추상화
- MeTal
- 네트워크 유량
- 네트워크 플로우
- 최단경로문제
- 부스트캠프 6기
- 강한 순환 참조
- WWDC21
- HIG
- 최단경로 문제
- WWDC17
- test coverage
- 최단경로 알고리즘
- WWDC19
- IOS
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |