Swift - Closure, Capture List

3 분 소요


공식 문서 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를 사용하지만, 타입을 굳이 명시 안 해도 됨

  • statement가 한 줄인 경우
      { (parameters(type 생략)) in
          statements(return 생략)
      }
    
    • ex)
        someArr.sorted { $0 > $1 }
      
  • 인수를 약식으로 쓰는 경우

      {
          statements($0, $1  사용)
      }
    
    • ex)
        someLabel.then {
                $0.text = "인수를 약식으로 적기"
        }
      
  • 연산자만 사용하는 경우
      {
          연산자
      }
    
    • ex)
        someArr.sorted(by: >)
      
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는 할당이 해제 됩니다(순환 구조는 없으므로).



굿


태그:

카테고리:

업데이트:

댓글남기기