Swift

lazy var 클로저에서 retain cycle이 생기는 경우

Phililip 2024. 6. 20.
728x90

# 1. 클로저가 바로 실행되는 경우 -> retain cycle 없음.

lazy var 클로저가 곧바로 실행되는 경우, 컴파일러에 의해 클로저는 @nonescape 처리가 되어 self를 캡처하지 않는다.

 

즉, retain cycle이 발생하지 않는다.

 

예를 들어, 아래처럼 lazy var의 클로저가 곧바로 실행된다면 retain cycle이 증가하지 않고,. 변수에 nil이 설정되면 deinit이 호출된다.

 

import UIKit
class MyView {
private let title = "text"
private lazy var label: UILabel = {
let label = UILabel()
label.text = self.title // self를 capture하지 않음!
return label
}()
func setup() {
print("Before using lazy var. retain Count => \(CFGetRetainCount(self))") // Before using lazy var. retain Count => 3
let label = self.label
print("After using lazy var. retain Count => \(CFGetRetainCount(self))") // After using lazy var. retain Count => 3
}
deinit {
print("Deinit!")
}
}
var myView: MyView? = MyView()
myView?.setup()
myView = nil // Print("Deinit")
view raw MyView.swift hosted with ❤ by GitHub

 

# 2. 클로저 자체를 lazy var로 정의한 경우 -> retain cycle 발생할 수 있음.

클로저는 reference type이다.

 

클로저 자체를 lazy var로 정의하면, lazy var를 사용하는 곳에서 클로저 인스턴스를 lazy 하게 생성된다.

 

생성된 후 클로저 내부가 실행되므로 클로저는 @escaping이다.

 

즉, 클로저 내부에서 self를 capture 하기 때문에 retain cycle이 발생할 수 있다. -> retain count 증가함

 

class MyView {
private let title = "text"
private lazy var label: () -> (UILabel) = {
let label = UILabel()
label.text = self.title // @escaping 클로저로 처리되어 self가 capture 됨.
return label
}
func setup() {
print("Before using lazy var. retain Count => \(CFGetRetainCount(self))") // Before using lazy var. retain Count => 3
let label = self.label()
print("After using lazy var. retain Count => \(CFGetRetainCount(self))") // After using lazy var. retain Count => 4
}
deinit {
print("Deinit!")
}
}
var myView: MyView? = MyView()
myView?.setup()
myView = nil // deinit 호출되지 않음!
view raw MyView.swift hosted with ❤ by GitHub

 

이때 클로저에서 [unowned self]나 [weak self]를 사용하면 retain cycle을 방지할 수 있다.

 

import UIKit
class MyView {
private let title = "text"
// 클로저에서 [unowned self]를 사용해서 retain cycle 방지
private lazy var label: () -> (UILabel) = { [unowned self] in
let label = UILabel()
label.text = self.title
return label
}
func setup() {
print("Before using lazy var. retain Count => \(CFGetRetainCount(self))") // Before using lazy var. retain Count => 3
let label = self.label()
print("After using lazy var. retain Count => \(CFGetRetainCount(self))") // After using lazy var. retain Count => 3
}
deinit {
print("Deinit!")
}
}
var myView: MyView? = MyView()
myView?.setup()
myView = nil // Print("Deinit")
view raw MyView.swift hosted with ❤ by GitHub

 

# 참고

 

[iOS - swift] lazy var 클로저 사용 주의 (리테인 사이클, 메모리 릭)

Lazy var 클로저 사용시 주의사항 lazy var 클로저 사용 시 retain cycle이 발생하는지? 아래 1)번과 2)번 구분 (아래에서 계속) 1) private let text = "label" private lazy var label: () -> UILabel = { let label = UILabel() label.

ios-development.tistory.com

 

Does lazy var capture self?

No. Immediately applied closure is automatically considered @noescape.

frouo.com

 


이번 글은 여기서 마무리.

 

 

 

반응형