종류: SwiftUI

한 줄 요약: apple만의 rxswift ㅎㅎ 기본적으로 Observable이 Publisher Observer가 Subscriber

단, Publisher는 프로토콜이고 AnyPublisher는 Publisher를 따르는 struct

Subscriber는 프로토콜이고 AnySubscriber는 Subscriber를 따르는 Struct

즉, 정확하게 얘기하면 Observable은 AnyPublisher와 같고 Obsesrver는 AnySubscriber와 같다!

Combine

  • 이벤트 처리 연산자들을 통해 비동기 이벤트를 핸들링 할 수 있는 애플 자체의 프레임워크
    • publisher를 정의해서 value를 expose
    • subscriber로 publisher로부터의 값을 받을 수 있음
  • publisher protocol은 타입을 정의
    • can deliver a sequence of values over time
    • publisher는 operator를 가지고 있어서 upstream publisher로부터 어떻게 value를 처리할지 그리고 republisher함
  • subscriber는 받아서 element로 행동

ex) Timer, notificationCenter, URLSession에서 publisher를 통해 expose했었음

  • combine은 key value observing를 준수하는 모든 속성에 대한 publisher를 제공
  • 여러 publisher의 결과를 결합하고 상호작용할 수 있음

Publisher

  • protocol
  • 시간에 따라 일련의 값을 전송할 수 있음을 선언
  • 여러 개의 subscriber instance한테 element를 전송할 수 있음
  • subcriber의 input과 failure의 타입은 무조건 publisher의 output, failure과 같아야 함
  • publisher는 subscriber를 허용하려면 receive라는 함수를 구현해야 함.
// Publisher: output, Failure, receive를 구현해야 함
    struct EventPublisher: Publisher {
        typealias Output = UIControl
        typealias Failure = Never
        
        let control: UIControl
        let event: UIControl.Event
        
        func receive<S>(subscriber: S) where S : Subscriber, Never == S.Failure, UIControl == S.Input {
            let subscription = EventSubscription(control: control, subscrier: subscriber, event: event)
            subscriber.receive(subscription: subscription)
        }
    }
    
    // Subscription: 구독권, element를 요구하거나 더이상 안받겠다라고 할 수 있음
    fileprivate class EventSubscription<EventSubscriber: Subscriber>: Subscription where EventSubscriber.Input == UIControl, EventSubscriber.Failure == Never {

        let control: UIControl
        let event: UIControl.Event
        var subscriber: EventSubscriber?

        init(control: UIControl, subscrier: EventSubscriber, event: UIControl.Event) {
            self.control = control
            self.subscriber = subscrier
            self.event = event

            control.addTarget(self, action: #selector(eventDidOccur), for: event)
        }

        func request(_ demand: Subscribers.Demand) {}

        func cancel() {
            subscriber = nil
            control.removeTarget(self, action: #selector(eventDidOccur), for: event)
        }

        @objc func eventDidOccur() {
            _ = subscriber?.receive(control)
        }
    }

Convenience Publishers

publisher를 만드는 방법(publisher 프로토콜을 채택한 struct)

  • Future: single value를 생성하고 끝나거나 fail하는 publisher
  • Just: 각 subscriber에게 하나의 output을 내놓고 끝내는 publisher
let publisher = Just("Zedd")
        
let subscriber = publisher.sink { (value) in // sink가 subscriber 인스턴스를 생성
      print(value) // "Zedd"
}
  • Deferred:
  • Empty: 아무 value로 publish하지 않고 finish immediately 가능
  • Fail: specifiec error와 함께 immediately terminate하는 publisher
  • Record:

Subscriber

  • protocol
  • publisher로부터 input을 받을 수 있는 타입을 선언하는 프로토콜
  • subscriber instance는 publisher의 element stream과 그들의 relationship의 변경사항을 설명하는 life cycle 이벤트를 받음

publisher값을 subscribe하는 방법

  1. subscribe메소드 이용해서(권장하지 않음)
    1. subscriber 프로토콜을 만족하는 class를 만들어서
     import UIKit
     import Combine
        
     class MySubscriber: Subscriber {
            
         typealias Input = String // 받을 타입의 종류
         typealias Failure = Never // 에러의 종류
            
         // 1 구독했음을 알리고 publisher에게 요청할 item의 수를 지정
         func receive(subscription: Subscription) {
             print("구독 시작이야!")
     //        subscription.request(.unlimited)
             subscription.request(.max(2))
         }
            
         // 2
         func receive(_ input: String) -> Subscribers.Demand {
             print("\(input)")
             return .none // no element, max(0)과 같은 의미, demand는 누적임!
         }
            
         // 3: 정상적으로 끝났는지 에어로 인해 끝났음을 알려주는 함수
         func receive(completion: Subscribers.Completion<Never>) {
             print("완료!", completion)
         }
     }
        
     // 구독하는 곳
     let publisher = Just("나는 써니")
     publisher.subscribe(MySubscriber())
        
     let puiblisher = ["sunny", "써니", "수빈"].publisher
     puiblisher.subscribe(MySubscriber())
        
     /* 결과
     구독 시작이야!
     나는 써니
     완료! finished
     구독 시작이야!
     sunny
     써니 <- 여기서 완료ㅕ가 안나옴 이유는 뒤에 수빈이 emit되지 않았기 때문!!
     */
    
  2. sink 함수 이용해서
    1. 위의 방법을 쉽게 하는 거라고 보면 됨!!! 무제한의 개수 요청하는 subscriber라고 보면 됨
    2. 가장 많이 사용하는 방법!
        
     @Published var input = ""
     $input
                .sink { print($0, $1) }
                .store(in: &cancellable)
    
  3. assign(to: on:) 이용해서!
    1. 새로운 값을 keypath에 따라 주어진 인스턴스의 property에 할당
    2. 단, sink와 달리 주어지는 값이 무조건 있어야 하기 때문에 publisher의 failure 타입이 Never일 때만 사용 가능!!
// 1. didSet을 통해 value 값이 바뀌면 새 값을 print합니다.
  class SomeObject {
    var value: String = "" {
      didSet {
        print(value)
      }
    }
  }
  
  // 2. 위에서 만든 class의 instance를 선언합니다.
  let object = SomeObject()

// 3. String 배열로 이뤄진 publisher를 생성합니다.
let publisher = ["Hello", "world!"].publisher
  
  // 4. publisher를 구독하면서 새롭게 받은 값을 object의 value에 할당합니다.
  _ = publisher
    .assign(to: \.value, on: object)

cf.

publisher.print().subscribe(MySubscriber()) //으로 하면 쉽게 print 가능!

Subject

  • Publisher랑 AnyObject를 따르는 프로토콜!!
  • 차이점은 밖에서 값을 방출할 수 있다!!!

publisher

publisher

subject

subject

@Published + 일반 변수

  • 일반 변수를 구독형으로 만들어 줌
  • 단 사용할 때 변수 앞에 $를 붙여줘야 함
  • 구독 시작하자마자 발행이 되기 때문에 Rx의 BehaviorSubject와 비슷해짐
  • 초기값이 없으면 실제 사용하는 곳에서 무지 귀찮아지니까 차라리 PassthroughSubject로 바꾸는게 좋아보임

PassthroughSubject

  • Rx의 PublishedSubject와 비슷
  • 구독 후 값이 들어왔을 때만 발행됨
  • 같은 값이 또 들어와도 발행됨

CurrentValueSubject

  • Rx의 BehaviorSubject와 비슷
  • 구독하자마자 현재 값이 발행됨
  • 무조건 초기값을 지정해야 함

✅Operator