Swift - Closure, Capture List
공식 문서 Closures 보고 정리했던 거
Closure
- 어떠한 context에서 모든 상수 및 변수에 대한 참조를 캡처하고 저장
- 캡처의 모든 메모리 관리를 처리
- 참조 타입
let closure2 = closure
closure2와 closure는 같음(동일한 클로저를 참조하므로)
Form
이름 | 값 캡처 | |
---|---|---|
전역 함수 | O | X |
중첩 함수 | O | O(둘러싸는 함수) |
클로저 표현식 | X | O(현재 컨텍스트) |
Syntax
- 기본
{ (parameters) -> return type in statements }
- 타입 추론이 가능한 경우
{ (parameters(without type)) in statements }
- ex)
Snapkit
view.snp.makeConstraints { make in make.top.right.bottom.equalToSuperview() make.left.equalToSuperview().inset(10.0) }
실제로는 Snapkit의
ConstraintMaker
를 사용하지만, 타입을 굳이 명시 안 해도 됨
- ex)
- statement가 한 줄인 경우
{ (parameters(type 생략)) in statements(return 생략) }
- ex)
someArr.sorted { $0 > $1 }
- ex)
-
인수를 약식으로 쓰는 경우
{ statements($0, $1 등 사용) }
- ex)
someLabel.then { $0.text = "인수를 약식으로 적기" }
- ex)
- 연산자만 사용하는 경우
{ 연산자 }
- ex)
someArr.sorted(by: >)
- ex)
Trailing
- 함수의 최종 인수일 때 사용 가능
- 인수 레이블 생략
- 클로저가 여러 개인 경우
-
ex) 애니메이션
UIView .transition(with: self, duration: duration, options: .transitionCrossDissolve) { [weak self] in self?.alpha = 0 } completion: { [weak self] _ in completion() }
animations:
는 생략, 뒤의completion:
은 적어 줌 -
ex) 커스텀 Rx 프로퍼티
class RadioButtons: UIControl { var selectedIndex: Int = 0 { didSet { sendActions(for: .valueChanged) } } }
extension Reactive where Base: RadioButtons { var selectedIndex: ControlProperty<Int> { return base.rx .controlProperty(editingEvents: UIControl.Event.valueChanged) { view in view.selectedIndex } setter: { view, newValue in view.selectedIndex = newValue } } }
-
함수 원형
func controlProperty<T>(editingEvents: UIControl.Event, getter: @escaping (Base) -> T, setter: @escaping (Base, T) -> Void) -> ControlProperty<T>
-
사용
radioButtons.rx.selectedIndex .subscribe { value in // ... }.disposed(by: disposeBag)
-
이외에도 네트워크 작업 등
-
Auto closure
func foo(closure: @autoclosure () -> ()) {
closure()
}
foo(closure: print("자동으로 클로저로 보내기"))
func foo2(param: Int, closure: @autoclosure () -> ()) {
closure()
}
foo2(param: 2, closure: print("자동으로 클로저로 보내기")
func foo3(closure: @autoclosure () -> (), param: Int) {
closure()
}
foo3(closure: print("자동으로 클로저로 보내기"), param: 2)
순서 같은 거 딱히 상관 없이 잘 돌아감
func foo3(closure: @autoclosure @escaping () -> (), param: Int) {
closure()
}
foo3(closure: print("자동으로 클로저로 보내기"), param: 2)
@escaping과 함께 쓸 수도 있음
Capture values
최적화
- 값이 클로저에 의해 변경 X
- 클로저가 생성된 후 값이 변경 X 인 경우, 값의 복사본을 캡처하고 저장
강한 참조 순환
클래스의 프로퍼티로 클로저를 할당, 클로저가 클래스의 인스턴스를 참조하여 캡처하면?
- 강한 참조 순환 생성
- 따라서 이를 없애기 위해 capture lists 사용
ex)
class SomeView: UIView {
var someVal = 0
lazy var action: () -> () = {
print(self.someVal)
// ...
}
}
어떤 클래스 SomeView는 action을 가지고 있습니다.
action은 SomeView의 인스턴스를 사용하는 클로저입니다(lazy인 이유: self가 init 된 후 호출할 수 있으므로).
// 사용
class OtherView: UIView {
var someView: SomeView? = SomeView()
func foo() {
someView.action()
}
}
어떤 다른 클래스 OtherView에서 SomeView를 프로퍼티로 가집니다.
=>: 강한 참조, ->: 약한 참조
someView => SomeView instance
SomeView instance => action closure
action closure => SomeView instance
현재 참조 상태는 위와 같습니다.
var someView = nil
따라서 OtherView에서 someView 프로퍼티에 nil을 할당해도 해당 SomeView는 실제로 할당이 해제 되지 않습니다(SomeView 내의 클로저 action과 아직 강한 참조가 남아 있기 때문).
ex) 해결: capture lists 정의
class SomeView: UIView {
var someVal = 0
lazy var action: () -> () = { [unowned self] in
print(self.someVal)
// ...
}
}
// 또는
class SomeView: UIView {
var someVal = 0
lazy var action: () -> () = { [weak self] in
print(self?.someVal)
// ...
}
}
=>: 강한 참조, ->: 약한 참조, ..>: 소유되지 않은 참조
someView => SomeView instance
SomeView instance => action closure
action closure ..> SomeView instance
현재 참조 상태는 위와 같게 됩니다.
var someView = nil
따라서 OtherView에서 someView 프로퍼티에 nil을 할당하면 해당 SomeView는 이제 해제가 됩니다(자신을 향한 강한 참조가 전부 사라졌으므로).
ex2)
class SomeView: UIView {
var action: (() -> ())?
}
이번에는 SomeView에서 action 클로저가 정의되지 않았다고 봅시다.
// 사용
class OtherView: UIView {
var someView: SomeView? = SomeView()
var someVal = 0
func foo() {
someView.action = {
print(self.someVal)
}
}
}
SomeView를 프로퍼티로 갖는 OtherView에서 someView의 action을 OtherView의 인스턴스를 이용해 정의합니다.
=>: 강한 참조, ->: 약한 참조, ..>: 소유되지 않은 참조
someView(OtherView instance) => SomeView instance
SomeView instance => action closure
action closure => OtherView instance
현재 참조 상태는 위와 같게 됩니다.
var someView = nil
OtherView에서 someView 프로퍼티에 nil을 할당하면 해당 SomeView는 할당이 해제 됩니다(순환 구조는 없으므로).
굿
댓글남기기