관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] 1. weak self 동작 이해하기 - retain cycle이 발생하는 경우 (#클로저, #escaping 클로저) 본문

iOS 응용 (swift)

[iOS - swift] 1. weak self 동작 이해하기 - retain cycle이 발생하는 경우 (#클로저, #escaping 클로저)

jake-kim 2023. 11. 4. 01:35

1. weak self 동작 이해하기 - retain cycle이 발생하는 경우 (#클로저, #escaping 클로저)

2. weak self 동작 이해하기 - 외부에 weak self 선언하고 클로저에서 사용하는 경우 (#캡처리스트 [weak self] 리펙토링)

weak self 개념

  • escaping closure에서 순환 참조를 발생시키는 케이스에서 캡쳐 리스트로 weak 참조를 사용
  • 순환참조란?
    • 생성자가 할당 해제된 후에도 인스턴스가 할당 해제되지 않도록 하는 reference 상 순환을 이루는 상태
    • 둘 이상의 인스턴스가 서로에 대한 strong 참조를 보유할 때 발생
  • 구체적인 내용은 이전 포스팅 글 참고

사전지식) 클로저란?

  • Closure(폐쇄): 클로저는 코드에서 전달 및 사용할 수 있는 2가지의 자체 기능을 가진 블록

사전지식) @escaping 클로저란?

  • 클로저의 실행이 지연되는 것
    • 이름이 escaping인 이유) 한 예로 메서드의 블록에서 지역변수들을 선언하고 사용하면 해당 메서드의 블록 실행이 종료될 때 같이 사라지는데, 메서드의 실행 블록이 종료되어도 아직 클로저가 살아있으면 그 것들을 escaping이라 명명

ex1) closure 인수가 지연되지 않으므로 @escaping 키워드를 붙이지 않아도 가능

class A {
    func escape(closure: (String) -> ()) {
        closure("test")
    }
}

ex2) 딜레이가 부여되는 동시에 @escaping을 붙이지 않으면 에러 

ex3) 또 다른 delay의 의미로 property에 저장하는 경우에서도 역시 @escaping 을 붙이지 않으면 에러

  • 즉 딜레이 되는 클로저가 발생하면 무조건 @escaping을 붙여야 사용이 가능
class A {
    private var closureEscaper: ((String) -> ())?

    func escape(closure: @escaping (String) -> ()) {
        print("escaping!")
        closureEscaper = closure
    }
}

retain cycle이 발생하는 경우

  • escaping 클로저에서 strong self로 참조했을때 무조건 retain cycle이 발생하지 않음

ex) esacping 클로저 준비

class A {
    func escape(closure: @escaping (String) -> ()) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            closure("test")
        }
    }
}

class B {
    var name = "Jake"
    let a = A()

    init() {
        a.escape { string in
            self.name = string
        }
    }

    deinit {
        print("DEINIT: B")
    }
}
  • 아래처럼 처리하면 "DEINIT: B"가 출력되면서 retain cycle이 발생하지 않은 것을 확인
var b: B? = B()
b = nil

// DEINIT: B
  • 만약 closure를 property에 저장하는 escaping closure의 경우에 storng self로 처리하면 retain cycle이 발생
class A {
    private var closureEscaper: ((String) -> ())?

    func escape(closure: @escaping (String) -> ()) {
        closureEscaper = closure
    }
}

class B {
    var name = "Jake"
    let a = A()

    init() {
        a.escape { string in
            self.name = string
        }
    }

    deinit {
        print("DEINIT: B")
    }
}

var b: B? = B()
b = nil

// retain cycle때문에 "DEINIT: B" 호출되지 않음
  • 여기서 알 수 있는 것: retain cycle은 @escaping에서 strong self로 잡아도 발생하지 않을 수 있음

정리

  • 클로저는 코드에서 전달 및 사용할 수 있는 2가지의 자체 기능을 가진 블록
  • escaping 클로저란 실행이 지연될 수 있는 클로저를 의미
  • 중요사항
    • retain cycle이 발생하는 경우는 escaping 클로저에서 weak self 안쓰면 발생한다? x
    • retain cycle이 발생하는 경우는 escaping 클로저이고 이 closure가 또 다른 property에 저장될 때 발생한다? o

* 전체 코드: https://github.com/JK0369/ExWeakSelfRetain

Comments