Closure Capturing

Groot·2022년 8월 24일
0

TIL

목록 보기
39/153
post-thumbnail

TIL

🌱 난 오늘 무엇을 공부했을까?

📌 Closure Capturing - 공식문서

📍 Capturing Values

  • Closure는 정의된 주변 Context에서 상수와 변수를 캡처 할 수 있습니다.
  • Closure는 상수와 변수를 정의한 원래 범위가 더 이상 존재하지 않는 경우에도 본문 내에서 해당 상수 및 변수의 값을 참조하고 수정할 수 있습니다.
  • Swift에서 값을 캡처할 수 있는 가장 간단한 형태의 Closure는 다른 함수의 본문 내에 작성된 중첩 함수입니다.
  • 중첩 함수는 외부 함수의 모든 매개변수를 캡처할 수 있으며 외부 함수 내에 정의된 모든 상수 및 변수도 캡처할 수 있습니다.
  • 다음은 incrementer라는 중첩 함수를 포함하는 makeIncrementer라는 함수의 예입니다.
  • 중첩된 incrementer() 함수는 주변 컨텍스트에서 runningTotal 및 amount의 두 값을 캡처합니다.
  • 이 값을 캡처한 후 incrementer는 호출될 때마다 runningTotal을 양만큼 증가시키는 클로저로 makeIncrementer에 의해 반환됩니다.
    func makeIncrementer(forIncrement amount: Int) -> () -> Int {
        var runningTotal = 0
        func incrementer() -> Int {
            runningTotal += amount
            return runningTotal
        }
        return incrementer
    }
  • makeIncrementer의 반환 유형은 () -> Int입니다. 즉, 단순한 값이 아닌 함수를 반환합니다.
  • 반환하는 함수에는 매개변수가 없으며 호출될 때마다 값을 반환합니다.
  • makeIncrementer(forIncrement:) 함수는 반환될 incrementer의 현재 누계를 저장하기 위해 runningTotal이라는 정수 변수를 정의합니다.
  • makeIncrementer(forIncrement:) 함수에는 인수 레이블이 forIncrement이고 매개변수 이름이 amount인 단일 Int 매개변수가 있습니다.
  • 이 매개변수에 전달된 인수 값은 반환된 증분 함수가 호출될 때마다 runningTotal이 얼마나 증가해야 하는지를 지정합니다.
  • makeIncrementer 함수는 실제 증가를 수행하는 incrementer라는 중첩 함수를 정의합니다.
  • 이 함수는 단순히 runningTotal에 금액을 추가하고 결과를 반환합니다.
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
  • incrementer() 함수에는 매개변수가 없지만 함수 본문 내에서 runningTotal 및 amount를 참조합니다.
  • 주변 함수에서 runningTotal 및 amount에 대한 참조를 캡처하고 자체 함수 본문 내에서 사용하여 이를 수행합니다.
  • 참조로 캡처하면 makeIncrementer 호출이 종료될 때 runningTotal 및 amount가 사라지지 않고 다음에 증분기 함수가 호출될 때 runningTotal을 사용할 수 있습니다.

    최적화를 위해 Swift는 해당 값이 클로저에 의해 변경되지 않고 클로저가 생성된 후 값이 변경되지 않은 경우 대신 값의 복사본을 캡처하고 저장할 수 있습니다.
    또한 Swift는 더 이상 필요하지 않을 때 모든 메모리 관리를 처리합니다.

  • 다음은 makeIncrementer실행의 예입니다.
    let incrementByTen = makeIncrementer(forIncrement: 10)
  • 이 예제는 호출될 때마다 runningTotal 변수에 10을 추가하는 incrementer 함수를 참조하도록 incrementByTen이라는 상수를 설정합니다.
  • 함수를 여러 번 호출하면 이 동작이 실제로 실행되는 것을 볼 수 있습니다.
    incrementByTen()
    // returns a value of 10
    incrementByTen()
    // returns a value of 20
    incrementByTen()
    // returns a value of 30
  • 두 번째 증분자를 생성하면 별도의 runningTotal 변수에 대한 자체 저장 참조가 있습니다.
    let incrementBySeven = makeIncrementer(forIncrement: 7)
    incrementBySeven()
    // returns a value of 7
  • 원래 incrementer(incrementByTen)를 다시 호출하면 runningTotal 변수가 계속 증가하고 incrementBySeven에 의해 캡처된 변수에는 영향을 미치지 않습니다.
    incrementByTen()
    // returns a value of 40

    클래스 인스턴스의 속성에 클로저를 할당하고 클로저가 인스턴스 또는 해당 멤버를 참조하여 해당 인스턴스를 캡처하는 경우 클로저와 인스턴스 사이에 강력한 참조 주기를 생성합니다.


📍 Closures Are Reference Types

  • 위의 예에서 incrementBySeven및 incrementByTen는 상수이지만 이러한 상수가 참조하는 'runningTotal' Closure는 캡처한 변수를 계속 증가시킬 수 있습니다.

  • 이는 함수와 Closure가 참조 유형 이기 때문 입니다.

  • 상수나 변수에 함수나 Closure를 할당할 때마다 실제로 해당 상수나 변수를 함수나 Closure에 대한 참조 로 설정하는 것입니다.

  • 위의 예에서 incrementByTen이 참조하는 것은 Closure의 선택이며 Closure 자체의 내용이 아니라 상수입니다.

  • 이것은 또한 두 개의 서로 다른 상수 또는 변수에 Closure를 할당하는 경우 해당 상수 또는 변수 모두가 동일한 Closure를 참조한다는 것을 의미합니다.

    let alsoIncrementByTen = incrementByTen
    alsoIncrementByTen()
    // returns a value of 50
    
    incrementByTen()
    // returns a value of 60
  • 위의 예는 alsoIncrementByTen을 호출하는 것이 incrementByTen을 호출하는 것과 동일함을 보여줍니다.

  • 둘 다 동일한 Closure를 참조하기 때문에 둘 다 동일한 누계를 증가시키고 반환합니다
    Capturing Values - 공식문서


📌 AutomaticReferenceCounting - 공식문서

📍 Resolving Strong Reference Cycles for Closures

  • 클로저 정의의 일부로 capture list를 정의하여 클로저와 클래스 인스턴스 간의 강력한 참조 주기를 해결합니다.
  • 캡처 목록은 클로저 본문 내에서 하나 이상의 참조 유형을 캡처할 때 사용할 규칙을 정의합니다.
  • 두 클래스 인스턴스 간의 강력한 참조 주기와 마찬가지로 캡처된 각 참조를 강력한 참조가 아닌 weak or unowned reference로 선언합니다.
  • weak or unowned의 적절한 선택은 코드의 다른 부분 간의 관계에 따라 다릅니다.

    Swift는 클로저 내에서 self의 멤버를 참조할 때마다 someProperty 또는 someMethod()보다 self.someProperty 또는 self.someMethod()를 작성하도록 요구합니다. 이것은 실수로 자신을 포착할 수 있다는 것을 기억하는 데 도움이 됩니다.

🔗 Defining a Capture List

  • 캡처 목록의 각 항목은 클래스 인스턴스(예: self) 또는 일부 값으로 초기화된 변수(예: delegate = self.delegate)에 대한 참조와 함께 weak 또는 unowned 키워드의 쌍입니다.
  • 이러한 쌍은 쉼표로 구분된 한 쌍의 대괄호 안에 작성됩니다.
  • 클로저의 매개변수 목록과 반환 유형이 제공되는 경우 캡처 목록을 앞에 둡니다.
    lazy var someClosure = {
        [unowned self, weak delegate = self.delegate]
        (index: Int, stringToProcess: String) -> String in
        // closure body goes here
    }
  • 컨텍스트에서 유추할 수 있기 때문에 클로저가 매개변수 목록이나 반환 유형을 지정하지 않는 경우 캡처 목록을 클로저의 맨 처음에 배치하고 그 뒤에 in 키워드를 배치합니다.
    lazy var someClosure = {
        [unowned self, weak delegate = self.delegate] in
        // closure body goes here
    }

🔗 Weak and Unowned References

  • 클로저와 클로저가 캡처하는 인스턴스가 항상 서로를 참조하고 항상 동시에 할당 해제될 때 클로저의 캡처를 소유되지 않은 참조로 정의합니다.

  • 반대로, 캡처된 참조가 미래의 어느 시점에서 0이 될 수 있는 경우 캡처를 약한 참조로 정의하십시오.

  • 약한 참조는 항상 선택적 유형이며 참조하는 인스턴스가 할당 해제되면 자동으로 nil이 됩니다.

  • 이를 통해 클로저 본문 내에서 존재 여부를 확인할 수 있습니다.

    캡처된 참조가 절대 nil이 되지 않으면 약한 참조가 아니라 항상 소유되지 않은 참조로 캡처되어야 합니다.

  • 소유되지 않은 참조는 위의 클로저에 대한 강력한 참조 주기의 HTMLElement 예제에서 강력한 참조 주기를 해결하는 데 사용하는 적절한 캡처 방법입니다.

  • 순환을 피하기 위해 HTMLElement 클래스를 작성하는 방법은 다음과 같습니다.

    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")
        }
    }
  • 이 HTMLElement 구현은 asHTML 클로저 내에 캡처 목록을 추가한 것을 제외하고는 이전 구현과 동일합니다.

  • 이 경우 캡처 목록은 [unowned self]로 "강력한 참조가 아닌 소유되지 않은 참조로 self 캡처"를 의미합니다.

  • 이전과 같이 HTMLElement 인스턴스를 만들고 인쇄할 수 있습니다.

    var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
    print(paragraph!.asHTML())
    // Prints "<p>hello, world</p>"
  • 캡처 목록이 제자리에 있을 때 참조가 어떻게 표시되는지는 다음과 같습니다.

  • 이번에는 클로저에 의한 self 캡처는 소유되지 않은 참조이며 캡처한 HTMLElement 인스턴스를 강력하게 유지하지 않습니다.

  • 단락 변수에서 강력한 참조를 nil로 설정하면 HTMLElement 인스턴스가 할당 해제됩니다. 이는 아래 예제의 초기화 해제 메시지 인쇄에서 볼 수 있습니다.

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

    Resolving Strong Reference Cycles for Closures - 공식문서

profile
I Am Groot

0개의 댓글