ARC, 클로저의 캡처 리스트
ARC Automatic reference Counting
heap(클래스) 메모리에 할당되는 메모리 관리를 위해
나를 가리키는 녀석이 몇 개이냐! 0개이면 heap 메모리에서 사라지게 하는 것이 ARC 모델의 핵심
- 클래스의 인스턴스
weak, unowned로 선언 시에 reference counting이 일어나지 않도록 방지
- 클로저
클로저의 캡처리스트 내에서 weak, unowned를 선언해서 RC가 올라가지 않도록 함
우선 먼저 weak self를 붙여주지 않은 경우다. 이 경우에는 강한 참조가 일어날 것이다.
class ViewController: UIViewController {
var name: String = "뷰컨"
func doSomething() {
DispatchQueue.global().async {
sleep(3)
print("글로벌큐에서 출력하기: \(self.name)")
DispatchQueue.main.async {
print("메인큐에서 출력하기: \(self.name)")
}
}
}
deinit {
print("\(name) 메모리 해제")
}
}
func localScopeFunction() {
let vc = ViewController()
vc.doSomething()
}
localScopeFunction()
뷰컨은 힙에 선언이 되어 만들어질 것이다. - 클래스니까
localScopeFunction은 스택 영역에서 만들어서 호출되는 것이고, 해당 함수에서 vc의 주소? 메모리 주소를 불러서 힙 영역의 뷰컨을 가리키게 되고, (RC +1 => 현재 RC는 1)
localScopeFunction이 동작해서 vc의 doSomething 함수가 동작하기 때문에 글로벌큐의 클로저는 실제로 힙 영역에서 뷰컨을 가리키게 되면서 강한 참조가 만들어지게 된다. (RC +1 => 현재 RC는 2)
그리고 doSomething 함수 실행이 종료돼서 스택 영역에서 localScopeFunction 실행이 끝나서 사라지겠지?
그러면 뷰컨을 가리키는 것이 하나 사라져서 RC -1이 될 것이다. (RC -1 => 현재 RC는 1)
클로저가 다 실행된 다음에 뷰컨 메모리 해제가 일어남
왜냐하면 클로저가 강한 참조를 통해서 RC를 1로 가지고 있기 때문이다.
그래서 원래 localScopeFunction가 사라졌음에도 불구하고 클로저가 vc를 계속 붙잡고 있음에도 불구하고, 클로저가 사라지고 나서야 RC가 0으로 바뀌고 뷰컨이 사라지고 메모리가 해제될 것이다.
강한 참조를 하고 있기 때문에 이런 일이 발생하고 있는 것이다.
그렇다면 강한 참조를 하지 않으면 어떤 일이 발생하는 것일까?
코드는 동일한 상태에서 weak self를 붙여줘봤다.
*weak self는 옵셔널 타입을 가지고 있다!
class ViewController1: UIViewController {
var name: String = "뷰컨"
func doSomething() {
// 강한 참조 사이클이 일어나지 않지만, 굳이 뷰컨트롤러를 길게 잡아둘 필요가 없다면
// weak self로 선언
DispatchQueue.global().async { [weak self] in
sleep(3)
print("글로벌큐에서 출력하기: \(self?.name)")
DispatchQueue.main.async {
print("메인큐에서 출력하기: \(self?.name)")
}
}
}
deinit {
print("\(name) 메모리 해제")
}
}
func localScopeFunction1() {
let vc = ViewController1()
vc.doSomething()
}
localScopeFunction1()
왜 위와 다르게 self.name이 Nil인 상황이 되어버릴까?!!!
스택 영역에 localScopeFunction이 동작하고 localScopeFunction 내에서 vc의 메모리 주소를 만들겠지.
힙 메모리에 뷰컨이 생성된다. 그리고 localScopeFunction의 vc의 메모리 주소가 힙 메모리의 뷰컨을 가리킬 것이다.
여기까지 같은데 weak self로 인해 글로벌 큐의 클로저가 뷰컨을 참조하긴 하되, 약하게 가리키고 있는 것이다.
그래서 RC가 +1이 일어나지 않는다. 이게 바로 weak self의 효과이다.
localScopeFunction가 종료되는 순간에 스택 메모리에서는 localScopeFunction이 사라지고, RC가 0으로 바뀌고,
뷰컨도 자연스럽게 사라지고, 클로저 내의 작업은 (sleep(3)으로 인해) 3초 후에 일어나기 때문에 뷰컨이 이미 사라진 상태에서 접근하기 때문에 self.name이 nil이 나오게 되는 것이다.
일반적으로 weak self일 경우에 객체가 nil인 경우에는 guard 구문을 통해서 return을 시켜서 작업을 종료시킨다.
guard let self = self else { return }을 통해서
클로저를 여러번 겹쳐서 쓸 경우에는 관습적으로 weak self를 가장 바깥 클로저에서 한 번만 쓴다고 한다더라...
'⭐️ 개발 > iOS Concurrency' 카테고리의 다른 글
[GCD] Swift Concurrency - GCD (1) | 2022.09.02 |
---|---|
[GCD] Dispatch Group (4) | 2022.08.29 |
[GCD] 섹션 1~3 요약정리 (3) | 2022.08.18 |