관리 메뉴

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

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

Refactoring (리펙토링)

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

jake-kim 2023. 11. 5. 01:19

1. weak self 동작 이해하기 - retain cycle이 발생하는 경우

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

weak self를 클로저 외부에서 사용 시 retain cycle이 발생할까?

  • 예제를 위해 retain cycle이 발생하는 코드 준비
class A {
    private var closureEscaper: ((String) -> ())?

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

class B {
    var name = "Jake"
    let a = A() // 1. B에서 A참조

    init() {
        // 클로저 내부에서 strong self 참조: retain cycle 발생 o
        a.escape { string in
            self.name = string // 2. A에서 B참조
        }
    }

    deinit {
        print("DEINIT: B")
    }
}
  • 여기서 B인스턴스를 만든 후 nil을 할당해도 메모리 해제 x
var b: B? = B()
b = nil

// deinit이 안불림
  • 외부에서 weak self 선언 시 retain cycle이 발생하지 않아서 b인스턴스는 deinit 호출
weak var weakSelf = self
a.escape { string in
    weakSelf?.name = string
}

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

/*
escaping!
DEINIT: B
*/
  • 외부에서 weak self한 후 옵셔널 바인딩: strong self로 다시 참조하므로 retain cycle 발생 o
weak var weakSelf = self
guard let weakSelf else { return }
a.escape { string in
    weakSelf.name = string
}

// deinit이 안불림

weak self 리펙토링

  • 하나의 함수 안에서 closure가 있는 함수를 사용하는 곳이 여러개가 있어, weak self를 다수 쓰는 경우가 존재
class ViewController: UIViewController {
    var someValue = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        someFunc1 { [weak self] in
            print(self?.someValue)
        }
        
        someFunc2 { [weak self] in
            print(self?.someValue)
        }
        
        someFunc3 { [weak self] in
            print(self?.someValue)
        }
    }
    
    func someFunc1(block: @escaping () -> ()) {
        // Some working...
    }
    
    func someFunc2(block: @escaping () -> ()) {
        // Some working...
    }
    
    func someFunc3(block: @escaping () -> ()) {
        // Some working...
    }
  • [weak self]를 매번 선언해주어도 되지만, 코드 중복으로 인해 외부에 weak self를 선언하여 사용해도 retain cycle없이 사용이 가능
    • weak self를 매번 선언해주지 않아도 아래처럼 간결하게 처리가 가능
weak var weakSelf = self

someFunc1 {
    print(weakSelf?.someValue)
}

someFunc2 {
    print(weakSelf?.someValue)
}

someFunc3 {
    print(weakSelf?.someValue)
}

주의사항) weak로 잡은 상태에서 옵셔널 바인딩하는 경우

  • 보통 클로저 안에서 아래처럼 옵셔널 바인딩을 수행
someFunc1 { [weak self] in
    guard let self else { return }
    print(someValue)
}
  • 하지만 외부에 weak self를 선언한 상태에서 다시 옵셔널 바인딩을 시도하면 strong self로 잡히는 것을 주의
weak var weakSelf = self
guard let weakSelf else { return } // <- strong self로 잡히므로 이 weakSelf를 closure안에서 쓰면 retain cycle

someFunc1 {
    print(weakSelf.someValue)
}

someFunc2 {
    print(weakSelf.someValue)
}

someFunc3 {
    print(weakSelf.someValue)
}

핵심 정리

  • 1). 클로저 내부에서 strong self 참조: retain cycle 발생 o
a.escape { string in
    self.name = string
}
  • 2). 외부에서 weak self: retain cycle 발생 x
weak var weakSelf = self
a.escape { string in
    weakSelf?.name = string
}
  • 3). 외부에서 weak self한 후 옵셔널 바인딩: strong self로 다시 참조하므로 retain cycle 발생 o
weak var weakSelf = self
guard let weakSelf else { return }
a.escape { string in
    weakSelf.name = string
}

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

Comments