ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Combine - Subscriber 2편
    IOS/Combine 2024. 3. 30. 17:41

    Combine - Subscriber 1편에선 sink를 통해 Subscriber를 구현했습니다.

    이번 글에선 assign(to:,on:) assing(to:) 방법을 통한 구독을 포스팅하겠습니다.


    1.  assign(to:on:)

     

    Publisher에서 방출받는 값을 Object의 KVO-Compliant property에 할당합니다.

     

    예제코드는 다음과 같습니다.

    class ExampleObject {
        var value: String = "" {
          didSet {
            print(value)
          }
        }
      }
    
      let exampleObject = ExampleObject()
    
      let publisher = ["Hello", "Combine"].publisher
    
      publisher
        .assign(to: \.value, on: exampleObject)

    이렇게 KVO Property처럼 동작하는 ExampleObject.value 에 값을 할당하려면 assign(to:on:)을 사용할 수 있습니다.

    이 메소드는 UIKit Labels, Text Views 등 UIConponet에 값을 할당 가능하며 아주 유용한 메소드입니다.

    KVO_Complian
    더보기

    Key-Value Observing(KVO)

    이 property의 경우 Objective-C 시절부터 존재해 온 프로퍼티들로 Publisher를 활용해 값을 방출하고 구독을 통해 전달받을 수 있는 메커니즘을 제공하는 프로토퍼티입니다.

    예) OperatingQueue의 .publisher(for: \.operationgCount) -> \.operatingCount(KVO property)

    위 예제는 애플이 기본적으로 제공해 주는 KVO이며 사용자가 커스텀한 KVO를 가지는 Object를 생성할 수 있는데 이와 관련된 내용은 다음글의 주제로 작성하겠습니다.


    2. assign(to:)

    asssign(to:)는 assign(to:on:)과 달리 @Published property wrapper로 선언된 변수에 방출된 값을 republish 할 때 사용되는 메소드입니다.

    assign(to:), assign(to:on) 차이점 
    포인트는 assign(to:)의 경우 AnyCancellable token을 반환하지 않고, @Published property가 메모리에서 해제될 때 같이 소멸하게 됩니다. 즉, republish 대상인 클래스와 생명주기가 같습니다.

    assign(to:)를 사용하면 assign(to:on) 사용 시 발생하는 strong ference cycle을 예방할 수 있습니다.

    더보기

    Assign(to:on:)의 강한 참조로 인한 메모리 누수 이슈는 combine이 weak, unowned 메커니즘을 assign(to:on:)에 사용할 수 없기 때문에 발생합니다. 예를 들어 다음 코드는 deinit이 실행되지 않습니다

    final class Bar: ObservableObject {
      @Published var input: String = ""
      @Published var output: String = ""
    
      private var subscription: AnyCancellable?
    
      init() {
        subscription = $input
            .filter { $0.count > 0 }
            .map { "\($0) World!" }
            .sink { [weak self] input in
                self?.output = input
            }
      }
    
      deinit {
        subscription?.cancel()
        print("\(self): \(#function)")
      }
    }
    
    // Ussage
    var bar: Bar? = Bar()
    let foo = bar?.$output.sink { print($0) }
    bar?.input = "Hello"
    bar = nil
    foo?.cancel()

    출처 : https://forums.swift.org/t/does-assign-to-produce-memory-leaks/29546/3 

     

    Does 'assign(to:)' produce memory leaks?

    It would be really nice to be able to pass a weak self into Assign like so: subscription = $input .filter { $0.count > 0 } .map { "\($0) World!" } .assign(to: \.output, on: weak self) // or [weak self]

    forums.swift.org

     

    bar = nil로 인해 메모리 해제를 시도하고 있습니다. 메커니즘은 다음과 같습니다.

    1. bar클래스 내부 input property를 output으로 맵핑하고 있는 subscription을 subscription 변수에 할당

    2. 메모리 해제 전이나 완료 이벤트 전까지 방출된 값을 output에 할당 중

    3. subscription 변수의 경우 Bar 인스턴스를 참조하고 있으므로 둘 사이에서 Strong Referecen Cycle이 발생

     

    따라서 Any 아래와 같이 변경되면 메모리 누수를 예방할 수 있습니다.

    init() {
        $input
            .filter { $0.count > 0 }
            .map { "\($0) World!" }
            .assign(to: &$output)
      }
    참고 
    @Published property wrapper 참조는 변수 앞에 $ 기호를 사용합니다.

     

    'IOS > Combine' 카테고리의 다른 글

    Combine Operators 1 (transforming)  (0) 2024.04.13
    Combine Cancellable, subscription  (0) 2024.04.07
    Combine - Subscriber 1편  (0) 2024.03.16
    Combine 구성요소 Publisher  (0) 2024.03.10
    Combine Framework란?  (0) 2024.03.03
Designed by Tistory.