ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 클로저에서 [weak self]를 사용하는 이유
    Swift 2021. 8. 21. 15:10

     

    목차

    1.  클로저에서 self를 weak로 캡쳐하는 이유

    2. 강한 참조 순환이 일어나는 경우

    3. 약한 참조를 통해 강한 참조 순환을 피하는 경우

    4. 결론


    클로저에서 self를 weak로 캡쳐하는 이유

    강한 참조 순환(Strong Reference Cycle)을 피하기 위함입니다. 클래스 속 클로저 안에 해당 클래스의 인스턴스(self)를 사용하기 위해서는 약한 참조를 해야합니다. self를 약한 참조로 캡쳐하지 않으면 강한 참조 순환(Strong Reference Cycle)이 일어나기 때문입니다.

    예시를 통해 자세히 설명하겠습니다.


    강한 참조 순환이 일어나는 경우

    class someClass {
        var someProperty: Int?
        var someClosure: (() -> Int?)?
    
        func someFunction() {
            someClosure = {
                return self.someProperty //self를 강한 참조하고 있습니다
            }
            print("someFunction이 실행되었습니다.")
            print("해당하는 someClass의 인스턴스(self)가 강한 참조되어 해당되는 someClass 인스턴스의 참조 카운트가 1 증가합니다.")
        }
    
        deinit {
            print("someClass가 메모리에서 해제되었습니다.")
        }
    }
    
    var classInstance: someClass? = someClass() //someClass 인스턴스(변수 classInstance에 할당된 someClass)의 참조카운트가 1 증가하여 1이 됩니다.
    classInstance?.someFunction()   //someClass 인스턴스의 참조 카운트가 1 증가하여 2가 됩니다.
    classInstance = nil //someClass 인스턴스의 참조카운트가 1 감소하여 1이 됩니다. someClass 인스턴스의 메모리가 해제되지 않습니다.

     

    실행결과 :

     

    someFunction 안에 있는 someClosure 의 클로저 안을 보시면 self를 통해 someProperty를 반환하고 있습니다. 이 과정을 통해 self를 참조함으로써 someClass 인스턴스의 참조 카운트가 1 증가합니다. 왜냐하면 self 가 의미하는 것은 변수 classInstance에 할당된 someClass의 인스턴스이기 때문입니다.

     

    순서대로 설명하자면,

    1. 변수 classInstancesomeClass의 인스턴스가 저장됩니다. 참조 카운트가 1 증가합니다.

    2. classInstance의 함수 someFunction을 호출합니다. someFunctionsomeClosure 에서 self 를 강한 참조하고 있습니다. weak 또는 unowned의 키워드 없이 self를 사용한다면 기본적으로 strong으로 참조되기 때문입니다. 결국 참조 카운트가 1 증가하여 2가 됩니다.

    3. classInstance 를 nil 로 변경합니다. 참조 카운트가 1 감소하여 1이 됩니다. Swift는 메모리 관리를 위해 ARC (Automatic Reference Counting)를 사용합니다. ARC는 참조 카운트가 0이 된다면 해당 인스턴스를 메모리에서 자동으로 해제시킵니다. 하지만 someClass의 인스턴스(classInstance에 할당된 someClass)는 참조 카운트가 여전히 1이기 때문에 ARC에 의해 해제되지 않습니다.

     

    이 때문에 강한 참조 순환이 일어나 someClass의 인스턴스가 메모리에 해제되지 않는 일이 일어납니다. 이 문제가 일어나지 않게 하기 위하여 클로저의 캡쳐리스트에 weak self 를 추가합니다.


    약한 참조를 통해 강한 참조 순환을 피하는 경우

    class someClass {
        var someProperty: Int?
        var someClosure: (() -> Int?)?
    
        func someFunction() {
            someClosure = { [weak self] in //self를 약한 참조로 캡쳐링합니다.
                return self?.someProperty
            }
            print("someFunction이 실행되었습니다.")
            print("본인의 someClass 인스턴스(self)가 약한 참조되어 해당하는 someClass 인스턴스의 참조 카운트가 증가하지 않습니다.")
        }
    
        deinit {
            print("someClass가 메모리에서 해제되었습니다.")
        }
    }
    
    var classInstance: someClass? = someClass() //someClass 인스턴스(변수 classInstance에 할당된 someClass)의 참조카운트가 1 증가하여 1이 됩니다.
    classInstance?.someFunction()   //someClass 인스턴스의 참조 카운트가 증가하지 않으므로 1입니다.
    classInstance = nil //someClass 인스턴스의 참조카운트가 1 감소하여 0이 됩니다. someClass 인스턴스의 메모리가 해제됩니다.

     

    실행 결과 :

     

    위의 코드는 첫번째 코드(강한 참조 순환이 일어나는 경우)와 똑같지만 someClosure 부분만 다릅니다. 처음의 코드는 self를 강한 참조하고 있지만 위의 코드는 클로저의 capture list에 [weak self]가 들어가있습니다. 이를 통해 self가 약한 참조되어 클로저가 실행되어도 self의 참조 카운트가 올라가지 않습니다.

     

    순서대로 설명하자면,

    1. 변수 classInstancesomeClass의 인스턴스가 저장됩니다. 참조 카운트가 1 증가합니다.

    2. classInstance의 함수 someFunction을 호출합니다. someFunctionsomeClosure 에서 self 를 약한 참조하고 있습니다. 약한 참조를 사용한다면 참조 카운트가 올라가지 않습니다. 결국 참조 카운트가 여전히 1 입니다.

    3. classInstance 를 nil 로 변경합니다. 참조 카운트가 1 감소하여 0이 됩니다. ARC는 참조 카운트가 0이 된다면 해당 인스턴스를 메모리에서 자동으로 해제시킵니다. 그리하여 someClass의 인스턴스(classInstance에 할당된 someClass)는 참조 카운트가 0 이기 때문에 ARC에 의해 메모리에서 해제됩니다.

     


    결론

    클로저에서 본인이 속한 인스턴스(self)를 참조할 때, 강한 참조를 이용한다면 강한 참조 순환이 발생할수 있습니다. 그러므로 weak self로 캡처링하여 강한 참조 순환을 피해야 합니다.

    unowned로 참조를 하여도 되지만 주의해야할 점이 있습니다. unowned는 바라보던 객체가 사라지면 댕글링 포인터가 남습니다. 아무것도 할당되지 않은 빈 공간을 바라보는 댕글링 포인터를 참조하게 된다면 crash가 나기 때문에 주의해야 합니다.

     

     

     

     

     

    Reference 

    https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html

     

    Automatic Reference Counting — The Swift Programming Language (Swift 5.5)

    Automatic Reference Counting Swift uses Automatic Reference Counting (ARC) to track and manage your app’s memory usage. In most cases, this means that memory management “just works” in Swift, and you don’t need to think about memory management your

    docs.swift.org

     

    'Swift' 카테고리의 다른 글

    참조 비교 연산자 ===  (0) 2021.08.28
Designed by Tistory.