본문 바로가기
iOS/App Services

Combine framework

by 탄이. 2020. 6. 16.

First look at Apple's new Combine framework - Q42 Engineering - Medium

Combine | Apple Developer Documentation

RxSwift와 Combine 비교

  • RxSwift에서 새로운 입력 Observables를 만드는 것은 매우 쉽습니다. 예를 들어 다음과 같이 할 수 있습니다.
// Using RxSwift Observables
let observable: Observable<Int> = Observable.create { observer in
  // 비동기 아무거나. (예를 들면 네트워크 요청)
  DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2), execute: {
    observer.onNext(42)
    observer.onCompleted()
  })
  return Disposables.create()
}
  • Combine에서 입력은 Publisher라고 하고 위와 같은 비동기는 이렇게 만들 수 있습니다.
struct MyPublisher : Publisher {

    public typealias Failure = Error
    public typealias Output = Int

    @available(iOS 13.0, *)
    public func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
        DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2), execute: {
            _ = subscriber.receive(42)
            subscriber.receive(completion: .finished)
        })
    }
}
  • 이 두 스니펫에서 사용 된 인터페이스는 약간 다릅니다.
  • 우선 Rx 구독 결과에서 구독을 중지 할 수있는 Disposable이 표시되며 Combine에는 이것이 없습니다.
  • 대신, 더 이상 업데이트를받지 않으려면 Publisher 체인의 참조를 취소하면 됩니다.
  • 또한 RxSwift의 onNext 반환 유형은 void입니다.
  • 스트림을 따라 데이터를 전달할 때 데이터 버블이 백업되지 않습니다.
  • Combine을 사용하면 subscriber.receive를 호출하면 현재 다운 스트림에서 처리 할 수있는 업데이트 수를 나타내는 Int를 반환합니다.
  • 이를 backpressure*라고하며, RxSwift 또는 RxJS에서는 사용할 수없는 기능입니다.
  • backpressure가 있는 RP 라이브러리에서 downstream 처리가 가능한 데이터가 더 많으면 backpressure가 '적용'됩니다.
  • backpressure를 가지거나 가지지 않거나는 설계 상의 선택이고 중요한 것은 Combine은 이것을 가지고 있다는 것입니다.

backpressure*

  • 역압, 배압(背壓)(기관의 배기측(排氣側)의 가스압(壓) 따위가 정상적인 압력 방향과 반대임)

Publishers

  • Publisher는 프로토콜이므로 그것을 구현하는 모든 구조체를 만들 수 있지만, Apple은 또한 Publishers (복수형) 열거 형에 사전 제작 된 Publishers 세트를 제공합니다.
  • 위 예제에서 우리는 구조체 방식의 비동기 Publisher를 만들었습니다. 그 대안은
@available(iOS 13.0, *)
let p = Publishers.Future { (subscriber: @escaping (Result<Int,Error>) -> Void) in
    DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2), execute: {
        subscriber(.success(42))
    })
}
  • 우리는 작업을 수행하는 함수 블록을 제공했습니다.
  • 원하는 지점에 subscriber closure를 붙이기만 하면, 이 함수 내에서 원하는 모든 것을 할 수 있습니다.
  • 다른 유용한 Publisher들
@available(iOS 13.0, *)
let p1 = Publishers.Empty<Any, Error>()

@available(iOS 13.0, *)
let p2 = Publishers.Just(42)

@available(iOS 13.0, *)
let p3 = Publishers.Sequence<[Int], Error>(sequence: [1,2,3,4])

Operators

  • Rx에서 알 수있는 여러 연산자가 있습니다. 예를 들어.
func run () {
    if #available(iOS 13.0, *) {

        // Create
        let p1 = Publishers.Empty<Int, Error>()
        let p2 = Publishers.Just(42)
        let p3 = Publishers.Sequence<[Int], Error>(sequence: [1,2,3,4])

        // Operators
        let p4 = p1
            .merge(with: p2)
            .append(5) // add 5 to the end of the sequence
            .allSatisfy { $0 >= 1 } // check if all values are bigger than 0
            .count() // how many values: 1
    }
}
  • 아직 전체 목록을 살펴 보진 않았지만 Rx 연산자의 대부분이 표현 된 것처럼 보입니다.
  • 그밖에는 스스로 구현하는 것이 아주 간단할 것입니다.

Subscribers

  • 스트림에서 값을 가져 오는 것은 Subscribers와 함께 작동합니다. 예를 들어 .sink
let p3 = Publishers.Just(42)
p3.sink { (value) in
    debugPrint(value)
}
  • 쉽습니다. 그러나 Combine의 힘에 대한 더 놀라운 예가 Subscribers.Assign subscriber입니다.
  • 예를 들어 다음에서 참조 유형 article의 제목 필드를 어떻게 동적으로 할당 할 수 있는지 알아보십시오.
@available(iOS 13.0, *)
func exampleCombineKVO () {
    let article = Article(title: "Test", body: "Lorum ipsum")
    // KeyPath 101의 경우, https://www.swiftbysundell.com/posts/the-power-of-key-paths-in-swift
    let keypath: ReferenceWritableKeyPath<Article,String> = \.title

    // 새로운 Combine 개체
    let sink = Subscribers.Assign(object: article, keyPath: keypath)

    let source = Publishers.Future<String, Never> { subscriber in
        DispatchQueue.main.async {
            subscriber(.success("42"))
        }
    }

    source.subscribe(sink)
}
  • 정말 멋진 기능입니다!

RxSwift와 Combine 간의 상호 운용성

  • 물론 두 리액티브 프레임워크는 함께 쓸 수 있다.
  • Combine으로 전체 코드베이스를 변환 할 필요가 없이, Observable을 SwiftUI에 넣는 것이 가능합니다.
  • 이렇게 하면 Observable에서 Publisher로 변환할 수 있다.
extension Observable : Publisher {
    @available(iOS 13.0, *)
    public func receive<S>(subscriber: S) where S : Subscriber, Observable.Failure == S.Failure, Observable.Output == S.Input {
        _ = self.subscribe(onNext: { (next) in
            let demand = subscriber.receive(next)
        }, onError: { (err) in
            subscriber.receive(completion: .failure(err))
        }, onCompleted: {
            subscriber.receive(completion: .finished)
        })
    }

    public typealias Failure = Error
    public typealias Output = Element
}
  • 반대도 가능해야 한다.
  • demand 속성은 사용되지 않는다는 점에 유의하십시오.
  • RxSwift는 back-pressure가 없으므로 전환 할 때 이미 조절 한 Observables 만 사용하고 UI 시스템을 오버런하지 않는지 확인해야합니다. ❓
  • 또한 떨어뜨리거나 버퍼링하여 back-pressure을 하는 일종의 동적 operator를 만들 수도 있을 것이다. ❓

마무리

  • 흥미롭게도 Combine은 Foundation 타입에 의존하지 않는다.
  • 사실, 그것은 Foundation보다 낮은 레벨에 있다.
  • 애플 엔지니어의 말을 인용

“RxSwift와 Combine의 메모리 모델은 매우 다르다. Combine은 정말 성능을 위해 만들어졌다.“

  • 기존의 Publisher를 보면, 애플은 이미 RealityKit 프레임워크와 같은 고성능 시스템에서 이것을 사용하고 있다.

  • 예를 들어 Scene.Publisher

    Scene.Publisher - Scene | Apple Developer Documentation

  • RxSwift와 비교했을 때, Combine은 매우 다른 구조(semantic)를 가지고 있다.

  • DisposeBags의 부재와 back-pressure의 존재는 아마도 Apple이 성능 및 통합에 미치는 직접적인 영향은 다른 낮은 수준의 시각화 및 AR 시스템에서 두 배로 감소함. ❓

  • 프레임률 종속 시스템 사용: 역압 사용, RP 시스템은 실제로 Rx와 같은 완전한 반응형 프레임워크보다는 장기 폴링/샘플링 시스템에 가깝다. ❓

  • 이 첫 번째 베타 릴리즈에서, 애플이 제공하는 Publisher들과 Sink 세트는 여전히 작다. ❓

  • 하지만 한 소식통에 의하면 다가올 릴리즈에는 URLSession, NotificationCenter & KVO와 같은 일반적인 Observable 항목을 위한 더욱 편리한 Publisher가 생길 것이라고 합니다.

댓글