Retain Cycle
- Closure의 경우 reference type이기 때문에
순환 참조(retain cycle)
를 발생시킬 수 있다.
class Person {
let name: String
// self가 확정난 후에 name을 참조해야 하기 때문에 lazy 변수로 설정
lazy var introduction: () -> String = {
return "저는 \(self.name) 입니다."
}
init(name: String) {
self.name = name
}
deinit{
print("deinit")
}
}
var leetaek: Person? = Person(name: "leetaek")
print(leetaek?.introduction())
leetaek = nil
// 출력: Optional("저는 leetaek 입니다.")
- leetaek 변수가 Person 객체를 참조하고, introduction closure의 Person의 name을 참조한다. (: reference count 2) → 때문에 deinit이 호출되지 않는다.
- weak var 의 경우 class 혹은 AnyObject를 상속받은 protocol에만 사용 가능하므로 introduction에는 사용할 수 없다.
Capture list
- 캡처 목록은 클로저가 생성될 때에 변경되지 않고 남아있기를 바라는 값들의 리스트를 뜻한다. 캡쳐된 값들은 클로저 외부의 코드가 종료되더라도 클로저 내부에 저장되어 남아있는다.
var str = "Hello, World!"
var myClosure = {
print (str)
}
str = "next"
let inc = myClosure
inc()
// 출력: next
var str = "Hello, World!"
var myClosure = { [str] in
print (str)
}
str = "next"
let inc = myClosure
inc()
// 출력: Hello, World!
weak, unowned
- 위와 같은 강한 참조로 인한 순환 참조 문제를 해결하고 싶다면 Capture list를
weak
,unowned
를 통해 가져오면 된다.
class Person {
let name: String
// self가 확정난 후에 name을 참조해야 하기 때문에 lazy 변수로 설정
lazy var introduction: () -> String = { [weak self] in
return "저는 \(self?.name) 입니다."
}
init(name: String) {
self.name = name
}
deinit{
print("deinit")
}
}
var leetaek: Person? = Person(name: "leetaek")
print(leetaek?.introduction())
leetaek = nil
// 출력: Optional("저는 leetaek 입니다.")
// deinit
- leetaek 변수가 Person 객체를 참조하고, introduction closure의 name을 weak로 참조한다. (: reference count 1) → deinit이 호출되지 않는다.
- unowned 로도 참조할 수 있지만 앱 크래쉬를 항상 조심해야 한다.
Non-escaping closure vs Escaping closure
Non-escaping closure
Non-escaping closure
는 나중에 저장되거나 실행될 수 없다. 때문에 메모리가 실제보다 늦게 해제되는delayed deinitialization
은 발생하지 않는다.
- 그렇지만 위의 예에서와 같이 순환 참조로 인한
Memory Leak
은 발생할 수 있다. → weak self로 해결
escaping closure
Escaping closure
는 프로퍼티에 저장되거나 다른 클로저에 넘겨져 선언 이후 시점에 실행될 수 있는 함수를 의미한다.
Escaping closure
는Non-escaping closure
와 동일하게 순환참조 문제로 인한memory leak
이 발생할 수 있다.
- 뿐만 아니라 내가 원하는 타이밍에 메모리 해체를 방해해
delayed deinitialization
가 생길 가능성도 있다. (ex. controller를 해체했지만 아직 closure 작업이 안끝났다던지)
→ 위 경우 모두 weak self로 해결 가능ㅎ
self? VS guard let self = self else { return }
- 옵셔널 체이닝 (self?)
- 옵셔널 체이닝이 달린 모든 self에 대해 nil 검사를 한다.
- self가 nil이 된다면 해당 메소드 호출을 멈춘다.
- 즉, 중간에 self가 해제되었는데도 계속 closure가 실행되는
delayed deinitialization
문제를 해결할 수 있다.
- 옵셔널 바인딩 ( guard let self = self else { return })
- self가 nil인지 확인하고 아닌 경우 일정 시간 동안 강한 참조를 일시 생성한다.
- self를 보장하므로, self가 먼저 메모리에서 해제되더라도 메소드가 계속 진행된다.
- 때문에
delayed deinitialization
로 이어질 수 있다.
→ 때문에 경우에 따라 어떤 방법을 사용해야 할지 생각할 필요가 있다.
Reference
- weak, unonwed
- capture list
Uploaded by N2T