클로저에 [weak self]를 넣어 순환 참조를 방지한 적이 있는가? 과연 순환 참조는 무엇이고 왜 어떻게 weak self로 순환 잠조를 막을 수 있는 것인지 알아보자.
순환 참조는 Strong Reference Cycle이라고도 하는데, 메모리 문제가 발생하는 원인이다. 우선 코드로 살펴보자.
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = john
Person 타입의 john과 Apartment 타입의 unit4A 변수를 초기화한 후, 서로를 참조하고 있다.
두 변수에 nil이 입력되면 서로를 참조하고 있던 것은 그대로 남아있다. 이게 메모리 누수의 원인이 되는 순환 참조다. 인스턴스는 사라졌지만 참조는 남아있기 때문에 의미 없는 메모리만 남게 되는 것이다.
john = nil
unit4A = nil
순환 참조가 쌓이면 앱이 죽거나 메모리 이슈가 발생한다. 메모리 누수가 쌓이면 배터리 소모량이 증가하고 동작이 느려지고 크래시가 발생한다. 그러므로 항상 메모리 누수를 조심하면서 코드를 작성해야 한다.
아하 이제 순환 참조가 뭔지 알겠다. 그럼 도대체 어떻게 weak self로 순환 참조를 막을 수 있다는 말인가.. weak에 대해서 먼저 알아야 한다.
weak은 프로퍼티나 변수 앞에 적는 키워드로 약한 참조를 의미한다. 약한 참조라는 것은 참조하는 인스턴스를 강하게 유지하지 않는 참조로, ARC가 참조된 인스턴스를 처리하는 것을 막지 않는다는 걸 말한다. 즉, ARC가 참조하는 인스턴스가 할당 해제되면 nil로 약한 참조를 자동으로 설정한다.
⁉️ ARC가 뭐죠..?
Automatic Reference Counting의 약자로 자동 참조 카운팅을 말한다. 앱의 메모리 사용량을 추적하고 관리하기 위해서 사용되는데, ARC는 인스턴스가 더 이상 필요하지 않을 때(참조 카운팅이 0일 때) 클래스 인스턴스에 의해 자동으로 메모리를 할당 해제한다.
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: Person? // weak 키워드로 선언한 약한 참조 변수!
deinit { print("Apartment \(unit) is being deinitialized") }
}
이렇게 변수 앞에 weak 키워드를 붙이면 아래와 같이 해당 변수를 강하지 않게 참조한다.
그래서 john 변수를 nil로 초기화하면 더 이상 Person 인스턴스에 대해 강한 참조를 가지지 않기 때문에 할당 해제된다.
john = nil
// Prints "John Appleseed is being deinitialized"
그래서 클로저에서도 [weak self]를 선언하면 클로저 안에서 강한 참조를 가지지 않기 때문에 더 이상 인스턴스가 사용되지 않는다고 판단하면 할당 해체하여 nil 값으로 설정되어 순환 참조가 발생하지 않는 것이다.
내용이 잘 이해가 가지 않는다면 "메모리 누수의 원인인 순환 참조를 방지하기 위해서 클로저 블록에 [weak self]를 선언한다."라고 알고 있자!
참고
https://bbiguduk.gitbook.io/swift/language-guide-1/automatic-reference-counting