[Swift] Closure / Self / Retain Cycle

이남준·2022년 4월 7일
0

let's take a closer look at closures

non-escaping closure

저장될 수 없고, 함수의 실행이 종료되기 전에 실행되는 클로저

escaping closure

프로퍼티, 혹은 다른 클로저에 capture되어 저장될 수 있는 클로저, 함수의 파라미터로 전달되었을 때 함수의 종료 후 실행되는 클로저

예시: 함수를 호출할 때 completion handler로 전달하는 클로저

escaping closure의 경우 정의되는 순간의 context를 저장해야한다.
context가 다른 value나 object를 포함하는 경우, 클로저가 실행될 때 존재를 (사라지지 않는 것) 보장하기 위해 capture되야한다.
-> 클로저가 정의될 때는 있었는데, 클로저가 실행되는 순간에 어떤 객체가 없으면 안되니까 정의되는 시점의 값을 capture

Self Capture와 Retain Cycle

class ListViewController: UITableViewController {
    private let viewModel: ListViewModel

    init(viewModel: ListViewModel) {
        self.viewModel = viewModel

        super.init(nibName: nil, bundle: nil)

        viewModel.observeNumberOfItemsChanged {
            self.tableView.reloadData()
        }
    }
}

View Controller 가 View Model을 참조하고 있고, View Model이 클로저 안에서 self로 View Controller를 참조하고 있기 때문에 Retain Cycle 발생
-> [weak self]로 해결

Delayed Deallocation

메모리 누수는 아니지만 의도하지 않은 동작이 일어날 수 있다.

viewModel.observeSomething { [weak self] in
    guard let self = self else { return }

    self.doSomethingHeavy1()
    self.doSomethingHeavy2()
    self.doSomethingHeavy3()
    self.doSomethingHeavy4()
}

doSomethingHeavy2()를 마치고, doSomethingHeavy3()를 실행하기 전에 self가 nil이 되도 guard let self = self에 의해 클로저 본문이나 범위가 살아있는 동안 self가 캡쳐되어 있기 때문에, self가 nil이 되는 순간 closure가 멈추지 않고 끝까지 실행되는 원치않는 동작이 일어날 수 있다.

viewModel.observeSomething { [weak self] in
    self?.doSomethingHeavy1()
    self?.doSomethingHeavy2()
    self?.doSomethingHeavy3()
    self?.doSomethingHeavy4()
}

대신 self?.를 사용하면, 매 호출마다 self가 nil인지 검사하기 때문에, self가 nil이 되는 순간 바로 더 이상 메소드를 호출하지 않을 수 있다.

Explicit / Implicit capturing

excaping closure의 경우 클로저 내부에서 self를 반드시 명시 해줘야 한다.
-> swift 5.3에서 명시 안해도 되게 변경됐음

profile
iOS 개발자의 기록

0개의 댓글