Upgrade to Pro — share decks privately, control downloads, hide ads and more …

It’s time to migrate from RxSwift to Combine. Long story short

It’s time to migrate from RxSwift to Combine. Long story short

Apple introduced Combine in pair with SwiftUI a year ago. However, it’s not necessary to convert all your UI to SwiftUI to start using this new reactive framework.

Usually, iOS apps support only the last two released iOS versions – so this fall some of you will finally drop support of iOS 12. Why not convert your existing RxSwift codebase to Combine then?

I’m going to tell you my story of forking an existing network library based on top of RxSwift and refactoring it with Combine.

https://github.com/chipp/miniGnomon

Vladimir Burdukov

September 08, 2020
Tweet

More Decks by Vladimir Burdukov

Other Decks in Programming

Transcript

  1. It’s time to migrate from RxSwift to Combine Long story

    short by Vladimir Burdukov 1 Mobile Z-days, Sep. 2020
  2. Introduction My name is Vladimir Burdukov. I'm a λ-addict since

    2013 and RxSwift user since 2015. One of authors of small Rx-based networking library Gnomon. Lead Software Engineer at EPAM by day. Rust maniac by night. 2 Mobile Z-days, Sep. 2020
  3. Agenda 1. Brief overview and comparison of RxSwift and Combine

    core elements. 2. Demo session. 3. Issues which I experienced during my transition to Combine. 3 Mobile Z-days, Sep. 2020
  4. RxSwift vs Combine RxSwift Combine Stream class Observable<Element> protocol Publisher

    (Output, Failure: Error) Receiver protocol ObserverType (Element) protocol Subscriber (Input, Failure: Error) Cancellation Disposable Cancellable and Subscription Subjects PublishSubject, BehaviorSubject PassthroughSubject, CurrentValueSubject 4 Mobile Z-days, Sep. 2020
  5. BlockingSubscriber CFRunLoop with/without timeout Implements Subscriber protocol func receive(subscription: Subscription)

    func receive(_ input: Input) -> Subscribers.Demand func receive(completion: Subscribers.Completion<Failure>) 6 Mobile Z-days, Sep. 2020
  6. receive(_ input: Input) func receive(_ input: Input) -> Subscribers.Demand {

    inputs.append(input) demand -= 1 if demand == .none { subscription?.cancel() stop() } return demand } 8 Mobile Z-days, Sep. 2020
  7. CombineLatestMany struct CombineLatestMany: Publisher {} class CombineLatestManySubscription: Subscription {} class

    CombineLatestManySubscription.Side: Subscriber {} 10 Mobile Z-days, Sep. 2020
  8. CombineLatestMany Publisher struct CombineLatestMany<Upstream: Publisher>: Publisher { typealias Output =

    [Upstream.Output] typealias Failure = Upstream.Failure func receive<S>(subscriber: S) /* generic constraints */ {} } 12 Mobile Z-days, Sep. 2020
  9. CombineLatestMany Publisher func receive<S>(subscriber: S) /* generic constraints */ {

    let subscription = CombineLatestManySubscription( publishers: publishers, subscriber: subscriber ) for (index, publisher) in publishers.enumerated() { publisher.subscribe( Side<Upstream.Output>(subscription, index: index) ) } subscriber.receive(subscription: subscription) } 13 Mobile Z-days, Sep. 2020
  10. CombineLatestMany Publisher func receive<S>(subscriber: S) /* generic constraints */ {

    let subscription = CombineLatestManySubscription( publishers: publishers, subscriber: subscriber ) for (index, publisher) in publishers.enumerated() { publisher.subscribe( Side<Upstream.Output>(subscription, index: index) ) } subscriber.receive(subscription: subscription) } 13 Mobile Z-days, Sep. 2020
  11. CombineLatestMany Publisher func receive<S>(subscriber: S) /* generic constraints */ {

    let subscription = CombineLatestManySubscription( publishers: publishers, subscriber: subscriber ) for (index, publisher) in publishers.enumerated() { publisher.subscribe( Side<Upstream.Output>(subscription, index: index) ) } subscriber.receive(subscription: subscription) } 13 Mobile Z-days, Sep. 2020
  12. CombineLatestMany Publisher func receive<S>(subscriber: S) /* generic constraints */ {

    let subscription = CombineLatestManySubscription( publishers: publishers, subscriber: subscriber ) for (index, publisher) in publishers.enumerated() { publisher.subscribe( Side<Upstream.Output>(subscription, index: index) ) } subscriber.receive(subscription: subscription) } 13 Mobile Z-days, Sep. 2020
  13. Issues Opaque types don't work for Publishers. func validateInput(_ input:

    String) -> some Publisher { guard !input.isEmpty else { return Fail(failure: ValidationError.emptyInput) } let request: Request<String> = generateRequest(input) return client.models(for: request).map(\.result) } 15 Mobile Z-days, Sep. 2020
  14. Opaque types func validateInput(_ input: String) -> some Publisher {

    guard !input.isEmpty else { return Fail(failure: ValidationError.emptyInput) // Fail<?, ValidationError> } let request: Request<String> = generateRequest(input) return client.models(for: request).map(\.result) // Publishers.Map<AnyPublisher<...>, String> } 16 Mobile Z-days, Sep. 2020
  15. Opaque types func validateInput(_ input: String) -> some Publisher {

    guard !input.isEmpty else { return Fail(failure: ValidationError.emptyInput) // Fail<?, ValidationError> } let request: Request<String> = generateRequest(input) return client.models(for: request).map(\.result) // Publishers.Map<AnyPublisher<...>, String> } 16 Mobile Z-days, Sep. 2020
  16. Opaque types func validateInput(_ input: String) -> some Publisher {

    guard !input.isEmpty else { return Fail(failure: ValidationError.emptyInput) // Fail<?, ValidationError> } let request: Request<String> = generateRequest(input) return client.models(for: request).map(\.result) // Publishers.Map<AnyPublisher<...>, String> } 16 Mobile Z-days, Sep. 2020
  17. Opaque types What is Output and Failure types for that

    publisher? let publisher: some Publisher = validateInput("Mobile People") // AnyPublisher<String, ValidationError> publisher.map(\.count) 17 Mobile Z-days, Sep. 2020
  18. eraseToAnyPublisher() extension Publisher { associatedtype Output associatedtype Failure: Error func

    eraseToAnyPublisher() -> AnyPublisher<Output, Failure> } 18 Mobile Z-days, Sep. 2020
  19. No testing toolchain from Apple Fortunately, iOS open-source community is

    very cool. EntwineTest is an analogue of RxTest with TestScheduler, TestPublisher and TestSubscriber. CombineExpectations – some kind of RxBlocking analogue based on XCTestCase. (?) CombineBlocking 19 Mobile Z-days, Sep. 2020
  20. Lack of familiar Rx operators No CombineLatestMany nor ZipMany nor

    withLatest(from:). Entwine – implements some of RxSwift operators. Feel free to take inspiration from OpenCombine to implement your own operators. 20 Mobile Z-days, Sep. 2020
  21. Conclusion It's good time to start transition of your code

    from RxSwift to Combine. Combine doesn't have so awesome community at the moment mostly because It's a closed source framework. There are many things to do both for Apple and the OSS community. 21 Mobile Z-days, Sep. 2020