본문 바로가기
Swift/기본

ARC 3편 (클로저에서의 강한 참조 순환)

by 밤새는탐험가 2024. 4. 26.

클로저에서의 강한 참조 순환

(Strong Reference Cycles for Closures)

 

클로저에서는 self를 캡쳐하기 때문에 강한 참조 순환이 발생할 수 있다. 

이를 해결하기 위해서 클로저 캡처 리스트를 사용한다. 

 

HTMLElement 클래스의 클로저 asHTML은 입력값을 받지 않고, 반환 값이 String인 () -> String 클로저를 사용한다.

이 클로저 안에서 self.text와 self.name과 같이 self를 캡쳐한다. 

 

✅ asHTML 클로저는 lazy로 선언되었다. name과 text가 준비되고 나서 HTML이 필요하고, lazy 프로퍼티 이기 때문에 프로퍼티 안에서 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 클로저는 다른 클로저로 변경될 수 있다.

 

let heading = HTMLElement(name: "h1")
let defaultText = "some default text"
heading.asHTML = {
    return "<\(heading.name)>\(heading.text ?? defaultText)</\(heading.name)>"
}
print(heading.asHTML())
// Prints "<h1>some default text</h1>"

 

 

아래 코드는 인스턴스와 클로저 간에 강한 참조를 하게 되어서 강한 순환 참조에 빠지게 된다.

 

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// Prints "<p>hello, world</p>"

 

 

✅ 클로저 안에서 self를 여러 번 참조하더라도 실제로는 단 한 번의 강한 참조만 캡쳐한다. 

paragraph에 nil을 할당해도 HTMLElement 인스턴스는 해제되지 않는다. 

 

 

 

 

클로저에서 강한 참조 순환 문제의 해결

(Resolving Strong Reference Cycles for Closures)

 

클로저에서 강한 참조 순환 문제를 해결하기 위해서 캡쳐 참조 대신 약한 참조 (weak) 또는 미소유 참조 (unowned)를 지정할 수 있다. 

 

 

캡쳐리스트 정의 (Defining a Capture List)

클로저의 파라미터 앞에 대괄호 [ ] 를 넣고 그 안에 각 캡쳐 대상에 대한 참조 타입을 적어준다. 

 

lazy var someClosure: (Int, String) -> String = {
    [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
    // closure body goes here
}

 

 

클로저의 파라미터가 없고 반환 값이 추론에 의해 생략 가능한 경우에는 캡처 리스트 정의를 in 앞에 적어준다. 

 

lazy var someClosure: () -> String = {
    [unowned self, weak delegate = self.delegate!] in
    // closure body goes here
}

 

 

약한 참조와 미소유 참조

(Weak and Unowned References)

 

인스턴스 참조와 마찬가지로 참조가 먼저 해제되는 경우는 약한 참조같은 시점이나 나중 시점에 해제되는 경우에는 미소유 참조를 사용한다. 

 

asHTML 클로저의 self에 [unowned self] 라고 캡쳐 리스트를 아래 코드와 같이 적어준다. 

 

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")
    }
}


var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// Prints "<p>hello, world</p>"

 

 

 

paragraph의 참조를 제거하면 HTMLElement 인스턴스가 바로 메모리에서 해제된다.

 

paragraph = nil
// Prints "p is being deinitialized"

 

 

'Swift > 기본' 카테고리의 다른 글

타입 추론, 타입 어노테이션  (0) 2024.04.27
타입 중첩  (1) 2024.04.26
DispatchQueue.main과 DispatchQueue.global  (1) 2024.04.20
ARC 2편 - 강한 참조 순환  (0) 2024.04.19
ARC 1편 - ARC 란?  (0) 2024.04.19