RxSwift遷移Combine指南

譯自 RxSwift to Combine: The Complete Transition Guide

不逐字翻譯了,只翻主要的知識點(diǎn)信息。

簡介

Combine 是swift新推出的一種面向 響應(yīng)式編程的框架。
很多開發(fā)者都想從RxSwift 切換到Combine,兩者之間有很多的相似之處。
但細(xì)節(jié)方面還是有很多區(qū)別的,如果你想切換過來,這篇文章會列出 RxSwift和Combine之間的 操作符,函數(shù),類型等等的映射。

響應(yīng)式編程可以讓 數(shù)據(jù)狀態(tài)在對象之間,工程之間,甚至app之間互相同步。
在ios開發(fā)中,最常見的就是在UI和Model之間進(jìn)行狀態(tài)同步。在之前很長一段時(shí)間里,RxSwift都是最好的選擇。但apple推出了 跟SwiftUI 更契合的Combine 作為響應(yīng)式編程的框架,基本可以完全替代RxSwift的所有功能了。

此文章將分為 Combine是怎么工作的 切換到Combine是好的選擇嗎 怎么更簡單地從RxSwift切換到Combine 三個(gè)部分。

RxSwift 和 Combine的主要區(qū)別

RxSwift Combine
支持的iOS版本 iOS 8.0+ iOS 13.0+
支持的平臺 iOS, macOS, tvOS, watchOS, Linux iOS, macOS, tvOS, watchOS, UIKit for Mac
框架所屬 第三方 Apple第一方,SDK內(nèi)置
誰來維護(hù)? 開源社區(qū) Apple技術(shù)團(tuán)隊(duì)
協(xié)作的UI框架 RxCocoa SwiftUI

Combine是怎么工作的

考慮到本文章是介紹如何從RxSwift遷移到Combine, 假設(shè)讀者已經(jīng)對RxSwift有一定的了解了。 如果需要了解RxSwift,可以參考 RxSwift repository on GitHub.

Combine在很多方面跟RxSwift都有相似之處以及對等的映射概念映射,比如 方法,類型聲明等。
如果要找出兩者之間更深層次的差異,需要對Combine進(jìn)行更深地挖掘,看看它的底層機(jī)制。

Publisher

跟RxSwift的Observable 相對應(yīng)的是Combine里的Publisher。
在RxSwift里是一個(gè)類,而在Combine里是一個(gè)協(xié)議。

protocol Publisher {
    associatedtype Output
    associatedtype Failure: Error
    func receive<S: Subscriber>(subscriber: S) 
        where Self.Failure == S.Failure, 
              Self.Output == S.Input
}

Combine 不會指定 Wrapper types來描述它的唯一特征,比如RxSwift里的Infallible, Maybe or Single。
但每個(gè)Publisher也有自己的自定義類型,借由該類型,Publisher的特征也能被推導(dǎo)出來。

RxSwift的Observable 需要實(shí)現(xiàn)subscribe 函數(shù),對應(yīng)的Publisher需要實(shí)現(xiàn) receive函數(shù),它們的功能基本一致。相比于RxSwift而言,Publisher指定了一個(gè) Failure類型,可以表明該publisher 是否/如何 失敗。對于那些不用處理失敗的Publisher,我們設(shè)定Failure類型為Never 即可。

Combine的Publisher 可以是 值類型(比如struct)或是 引用類型(比如類)。大多數(shù)的Publisher都是值類型的。

在RxSwift里,操作符一般就是簡單地返回一個(gè)Observable 類型,而Combine里返回的是一個(gè)混合類型,比較復(fù)雜;比如Timer.TimerPublisher, CombineLatest<Just<Int>, Just<Int>>. 鑒于自己定義一個(gè)混合類型比較麻煩,我們可以就用 AnyPublisher 來簡化Publisher的使用。
舉個(gè)例子:AnyPublisher<Int, Never>,可以作為一個(gè) 不會失敗的拋出Int值的Publisher。

Image

Subscriber

Subscriber (RxSwift: Observer)可以接收訂閱信息(subscriptions), 輸入數(shù)據(jù)(inputs),以及結(jié)束回調(diào)(completions). 這點(diǎn)跟RxSwift不同,沒有聲明包含了數(shù)據(jù)回調(diào)以及 結(jié)束回調(diào)的event的枚舉,但提供了單獨(dú)的方法來分別處理對應(yīng)的事件。

protocol Subscriber: CustomCombineIdentifierConvertible {
    associatedtype Input
    associatedtype Failure: Error

    func receive(subscription: Subscription)
    func receive(_ input: Self.Input) -> Subscribers.Demand
    func receive(completion: Subscribers.Completion<Self.Failure>)
}

在上面的代碼片段里,你可以看到Subscriber 也聲明了一個(gè)Failure 類型,決定了它能失敗,以及失敗的相關(guān)類型。對應(yīng)的RxSwift也聲明了獨(dú)立的Error事件。Combine非常明顯的表明了 失敗會導(dǎo)致Publisher 終結(jié)(RxSwift里,error事件也表明了 Observable的終結(jié))。

Subscribers 必須是類,因?yàn)?subscription要引用它來做數(shù)據(jù)傳遞,而不能在訂閱期間重新創(chuàng)建多次Subscribers(值類型比如struct 會在數(shù)據(jù)變化時(shí)會重新創(chuàng)建實(shí)例)。

Subscription

每當(dāng)你訂閱時(shí),就會創(chuàng)建一個(gè) Subscription,它會持有所有必要的資源的引用,而且可以隨時(shí)cancel(對應(yīng)RxSwift里的DisposeBag)。這也是為什么Subscription 必須是類。

protocol Subscription: Cancellable, ... {
    func request(_ demand: Subscribers.Demand)
}

protocol Cancellable {
    func cancel()
}

與RxSwift相比,Combine允許訂閱者隨時(shí)可以去獲取更多數(shù)據(jù),并以 backpressure support 的形式返回;因此 實(shí)際上是訂閱者在主動請求數(shù)據(jù)而不是 Publisher來決定什么時(shí)候?qū)?shù)據(jù)傳出去。
這能讓訂閱者不會被大量數(shù)據(jù)堵塞而導(dǎo)致無法及時(shí)處理,這看上去更像是可控制的數(shù)據(jù)流。

如果想更深入地了解 backpressure support ,可以參考 Processing Published Elements with Subscribers

Subject

Subject 跟RxSwift的差不多,可以看作是 Subscriber 和 Publisher 的組合,既可以發(fā)送數(shù)據(jù)也能接收數(shù)據(jù)。

protocol Subject: AnyObject, Publisher {
    func send(_ value: Self.Output)
    func send(completion: Subscribers.Completion<Self.Failure>)
    func send(subscription: Subscription)
}

一方面你可以將數(shù)據(jù)傳給 Subject,另一方面由于 Subject 遵從了 Publisher協(xié)議,它也可以被監(jiān)聽來獲取數(shù)據(jù)。

在Combine里,訂閱者 receive 而 Subject send 事件。盡管如此,在每個(gè)subject里,每當(dāng)value 遵從 Subscriber協(xié)議時(shí),你都必須創(chuàng)建一個(gè) AnySubscriber

綜上所述,RxSwift和Combine 大部分功能都是一樣的,但在接口方面有一些很小的區(qū)別。 那么哪個(gè)更適合我們呢?

切換到Combine是好的選擇嗎

最大的爭議點(diǎn)在于,Combine是apple的第一方框架,使用它你就不用做一些多余的framework 依賴配置工作,app也體積也會小一些。

當(dāng)然,RxSwift也沒有明確表明是否會有錯(cuò)誤發(fā)生。一般情況下,直接就用 Observable 然后等著數(shù)據(jù)過來就行了。 但在Combine里,就明確表明了Publisher 可能會失敗,而且失敗后產(chǎn)生的錯(cuò)誤是啥。

但這些可失敗和不會失敗的publisher的交互可能會非常復(fù)雜,你得在每個(gè)使用處都加上 eraseToAnyPublisher(),但幫助也不大。

backpressure support 的幫助下,Combine 能考慮得更加周到。訂閱者可以隨時(shí)獲取數(shù)據(jù),而不用等待Publisher 來發(fā)送數(shù)據(jù)。這更符合我們的直覺,特別是在一些很耗時(shí)的操作發(fā)生時(shí),大量的數(shù)據(jù)同時(shí)在publisher 進(jìn)出。

簡單來說,Combine 更適應(yīng)整個(gè)swift的環(huán)境。很多擴(kuò)展方法已經(jīng)在常用的類型里實(shí)現(xiàn)了(比如 Timer.publisher, (0...3).publisher, Optional<String>.none.publisher),這些方法讓Combine的功能更加的全面。

不過Combine不像RxSwift那樣全面和強(qiáng)大,是么?

RxSwift 沒有像Combine 那么多的編譯問題。RxSwift不用管 失敗的錯(cuò)誤類型,而且單個(gè)泛型類型的檢查比兩個(gè)要簡單得多。

RxSwift支持iOS9以及以上版本,但Combine只能用在iOS13 以及以上版本。它倆在所有的apple 平臺上都能work,但Combine對于linux的支持還是有缺失。OpenCombine 可以幫助解決這個(gè)問題,它用的是跟Combine一樣的接口,且能支持更多平臺。

Combine的命名規(guī)則更契合iOS平臺,而RxSwift 更契合跨平臺模式的規(guī)則。

RxSwift的技術(shù)社區(qū)已經(jīng)存在很長一段時(shí)間了,在維護(hù)和擴(kuò)展框架方面已經(jīng)做了很多改進(jìn)了。社區(qū)成員們?yōu)镽xSwift 擴(kuò)展了很多關(guān)于特定場景的功能,包括很多自定義的操作符和類型等。而Combine是一個(gè)封閉的框架,而且只推出了三年而已,只在少部分的項(xiàng)目里被使用到,如果你需要自己寫一些字定義的操作符,還不如直接使用現(xiàn)有的知識點(diǎn)來實(shí)現(xiàn)。

現(xiàn)在你要寫一個(gè)字定義的Publisher比自定義的Observable要麻煩許多。在RxSwift里,你只要用 Observable.create 來發(fā)送數(shù)據(jù),發(fā)送錯(cuò)誤,以及完成事件即可。在Combine里卻沒有對應(yīng)的操作,你可以用Future (基本上就是一個(gè)帶completion回調(diào)的Publisher,且completion只會調(diào)用一次) 或是寫一個(gè)自定義的Publisher 來模擬 Observable.create 的行為(我們接下來就會講到這點(diǎn))

總結(jié)一下

回到我們之前的 該用RxSwift還是Combine的問題,這個(gè)要看情況的。
如果你想要干掉之前那些“多余的”依賴關(guān)系,而且你的app支持iOS13 以及以上版本,且不用支持iOS 以外的平臺,那Combine就是比較好的選擇了。

怎么更簡單地從RxSwift切換到Combine

我們搜集了下面的切換引導(dǎo)信息,你可以直接搜索RxSwift的方法,操作符來找到Combine里的對應(yīng)替換方案。

一些簡單的說明

一般來說,Combine在處理錯(cuò)誤時(shí),相關(guān)接口看上去比較復(fù)雜。給個(gè)小提示, setFailureType(to: Failure.self) 更容易理解,而不是當(dāng)作 mapError { _ -> Failure in } 來看待。

下面的這些鏈接在我們從RxSwift切換到Combine時(shí)提供了很大的幫助。

  • CombineExt 里是Combine技術(shù)社區(qū)提供的一些 當(dāng)前系統(tǒng)Combine里沒有支持的操作符。
  • cheat sheet 也提供了不少幫助。

類型

Disposable

RxSwift Combine
Disposable Cancellable Use AnyCancellable to create them like RxSwift’s Disposables.create
DisposeBag Set<AnyCancellable> any RandomAccessCollection of AnyCancellable of course, you can also create references one by one

?? 注意,subscriptions 會在 Cancellable 析構(gòu)后直接cancel掉。

Publishers

RxSwift Combine
Observable<Element> AnyPublisher<Element, Error>或者 其他的 Publisher 類型
同樣根據(jù)拋出的錯(cuò)誤類型,Combine也能創(chuàng)建對應(yīng)的Failure類型
Single<Element> AnyPublisher<Element, Error>
Future<Element, Error>
可惜不能保證只拋出一個(gè)值,除非使用Future publisher
ConnectableObservable<Element> ConnectablePublisher
Infallible<Element> AnyPublisher<Element, Never>
注意:Failure在這里的類型是 Never
Maybe<Element> 在Combine里沒有對應(yīng)的東東

Subjects

RxSwift Combine
BehaviorSubject CurrentValueSubject
PublishSubject PassthroughSubject
ReplaySubject Combine里沒有
?? CombineExt里的替代方案
AsyncSubject Combine 里沒有

Relays

Relay是Subject的一個(gè)變種,他跟Subject的不同之處在于 無法發(fā)送/接收 完成事件。

class Relay<SubjectType: Subject>: Publisher,
    CustomCombineIdentifierConvertible where SubjectType.Failure == Never {

    typealias Output = SubjectType.Output
    typealias Failure = SubjectType.Failure

    let subject: SubjectType

    init(subject: SubjectType) {
        self.subject = subject
    }

    func send(_ value: Output) {
        subject
            .send(value)
    }

    func receive<S: Subscriber>(subscriber: S) 
        where Failure == S.Failure, Output == S.Input {
        subject
            .subscribe(on: DispatchQueue.main)
            .receive(subscriber: subscriber)
    }

}

typealias CurrentValueRelay<Output> = Relay<CurrentValueSubject<Output, Never>>
typealias PassthroughRelay<Output> = Relay<PassthroughSubject<Output, Never>>

extension Relay {

    convenience init<O>(_ value: O) 
        where SubjectType == CurrentValueSubject<O, Never> {
        self.init(subject: CurrentValueSubject(value))
    }

    convenience init<O>() 
        where SubjectType == PassthroughSubject<O, Never> {
        self.init(subject: PassthroughSubject())
    }

}

Observer Operators

RxSwift Combine
asObserver() AnySubscriber
on(_:) Subscriber
? receive(_:)
? receive(completion:)
Subject
? send(_:)
? send(completion:)
onNext(_:) Subscriber
? receive(_:)
Subject
? send(_:)
onError(_:) Subscriber
?receive(completion: .failure(<error>))
Subject
? send(completion: .failure(<error>))
onCompleted() Subscriber
? receive(completion: .finished)
Subject
? send(completion: .finished)
mapObserver(_:) Combine里沒有對應(yīng)的,不過實(shí)現(xiàn)起來很簡單
??replacement in CombineExt

如果要修改subscriber的 InputFailure 類型,可以使用下面這個(gè)擴(kuò)展方式

extension Subscriber {

    func map<Input>(
        _ map: @escaping (Input) -> Self.Input
    ) -> AnySubscriber<Input, Failure> {
        .init(
            receiveSubscription: receive,
            receiveValue: { self.receive(map($0)) },
            receiveCompletion: receive
        )
    }

    func mapError<Failure>(
        _ map: @escaping (Failure) -> Self.Failure
    ) -> AnySubscriber<Input, Failure> {
        .init(
            receiveSubscription: receive,
            receiveValue: receive,
            receiveCompletion: { completion in
                switch completion {
                case let .failure(error):
                    self.receive(completion: .failure(map(error)))
                case .finished:
                    self.receive(completion: .finished)
                }
            }
        )
    }
}

Scheduling

RxSwift 有很多不同的Scheduler 類型,比如:MainScheduler,ConcurrentDispatchQueueScheduler, SerialDispatchQueueScheduler, CurrentThreadScheduler, HistoricalScheduler, OperationQueueScheduler,以及其他更多類型。

而在Combine里,這個(gè)就很簡單了??梢灾苯邮褂肎CD(Grand Central Dispatc)的相關(guān)類型就可以了。比如 DispatchQueue, OperationQueue 以及 RunLoop。
Combine里唯一的Scheduler類型只有 ImmediateScheduler,它能讓任務(wù)直接同步執(zhí)行(跟CurrentThreadScheduler 差不多)。

這篇文章里可以獲取更多關(guān)于Combine里的scheduling 的信息。

Factory Functions

RxSwift Combine
amb(...) Combine里沒有
??replacement in CombineExt
catch(sequence:) Combine里沒有
combineLatest(...) Publishers.CombineLatest
Publishers.CombineLatest3
Publishers.CombineLatest4
Publisher.combineLatest(_:)
注意:你可以把它們串起來使用,然后用map轉(zhuǎn)成特殊的數(shù)據(jù)類型來使用
concat(...) Publishers.Concatenate
Publisher.append
不支持多于兩個(gè)值,可以用reduce 來達(dá)到目的
create(_:) 單個(gè)值的Publisher可以用 Future
多個(gè)值的Publisher可以參考 replacement in CombineExt
deferred(_:) Deferred
empty() Empty
error(_:) Fail
from(_:) Collection.publisher
Optional.publisher
generate(
initialState:
condition:
iterate:
)
Combine里沒有
just(_:) Just
merge(...) Publishers.Merge
Publishers.Merge3
Publishers.Merge4
Publishers.Merge5
Publishers.Merge6
Publishers.Merge7
Publishers.Merge8
Publishers.MergeMany
never() Empty(completeImmediately: false)
of(...) Collection.publisher
range() Collection.publisher可以用Range或ClosedRange
用 stride
repeatElement(_:) Combine里沒有
timer(_:period:scheduler:) Timer.publish
+ delay
+ autoconnect
using(_:observableFactory:) Combine里沒有
zip(...) Publishers.Zip
Publishers.Zip3
Publishers.Zip4
??replacement in CombineExt

Publisher / Observable Operators

RxSwift Combine
amb(_:) Combine里沒有
?? replacement in CombineExt
asCompletable() Combine里沒有
asObservable() eraseToAnyPublisher()
buffer(
timeSpan:
count:
scheduler:
)
collect(
.byTimeOrCount(::_:),
options: )
catch(:)
catchError(
:)
catch(_:)
tryCatch(_:)
catchAndReturn(:)
catchErrorJustReturn(
:)
replaceError(with:)
compactMap(_:) compactMap(_:)
tryCompactMap(_:)
concat(...) append
Publishers.Concatenate
concatMap(_:) Combine里沒有
可以用 reduce(::)append(_:)來實(shí)現(xiàn)
debounce(_:scheduler:) debounce(
for:
scheduler:
options: )
debug(
_:
trimOutput:
file:
line:
function:
)
print(_:to:)
delay(_:scheduler:) delay(
for:
tolerance:
scheduler:
options:
)
delaySubscription(
_:
scheduler:
)
Combine里沒有
可以用 Deferreddelay(for:scheduler:options:)的組合來替代
dematerialize() Combine里沒有
?? replacement in CombineExt
distinctUntilChanged(...) removeDuplicates()
當(dāng)Output是Equatable時(shí)用
removeDuplicates(by:)
tryRemoveDuplicates(by:)
do(
onNext:
afterNext:
onError:
afterError:
onCompleted:
afterCompleted:
onSubscribe:
onSubscribed:
onDispose:
)
handleEvents(
receiveSubscription:
receiveOutput:
receiveCompletion:
receiveCancel:
receiveRequest:
)

沒有onDispose: 在publisher complete時(shí),cancel不會被調(diào)用。
element(at:) output(at:)
enumerated() Combine里沒有
建議可以用 scan(::)魔改一下
filter(_:) filter(_:)
tryFilter(_:)
first() first()
flatMapFirst(_:) Combine里沒有
flatMap(_:) flatMap(_:)
tryFlatMap(_:)
flatMapLatest(_:) map(_:)接上 switchToLatest()
?? replacement in CombineExt
groupBy(keySelector:) 沒有
ifEmpty(default:) replaceEmpty(with:)
ifEmpty(switchTo:) 沒有
ignoreElements() ignoreOutput()
map(_:) map(_:)
tryMap(_:)
materialize() 沒有
?? replacement in CombineExt
multicast(_:) multicast(subject:)
multicast(makeSubject:) multicast(_:)
observe(on:) observe(on:)
Combine用的是不同的scheduler類型!
publish() multicast { PassthroughSubject() }
makeConnectable()
reduce(into:_:) 沒有
不過可以用 reduce 來實(shí)現(xiàn)
reduce(::) reduce(::)
tryReduce(::)
reduce(
_:
accumulator:
mapResult:
)
reduce(::)接上 map(_:)tryMap(_:)
refCount() autoconnect()
replay(_:) 沒有
可以用 multicast(_:)和 ReplaySubject 來實(shí)現(xiàn)
replayAll() 沒有
retry() retry(.max)
可能有微小的意義上的差別,但實(shí)際使用中一般沒區(qū)別
retry(_:) retry(_:)
retry(when:) 沒有
sample(_:defaultValue:) 沒有
scan(into:_:) 沒有
可以用一般的 scan(::)來實(shí)現(xiàn)
scan(::) scan(::)
tryScan(::)
share(replay:scope:) share()
或者用 multicast(_:)加上 PassthroughSubjectautoconnect()
或者用 multicast(_:)加上 ReplaySubject
?? extension in CombineExt
single() 沒有
當(dāng)有且只有一個(gè)值時(shí),用 first(),否則會報(bào)錯(cuò)
single(_:) 沒有
可以用 filter(_:)接上 single()的替代方案
skip(_:) dropFirst(_:)
skip(while:) drop(while:)
skip(until:) drop(untilOutputFrom:)
startWith(...) prepend(...)
subscribe(_:) 最類似的接口:
sink(
receiveValue:
receiveCompletion:
)
subscribe(on:) subscribe(on:options:)
Combine用的是不同的scheduler類型!
subscribe(
onNext:
onError:
onCompleted:
onDisposed:
)
最類似的接口:
sink(
receiveValue:
receiveCompletion:
)
subscribe(
with:
onNext:
onError:
onCompleted:
onDisposed:
)
最類似的接口:
sink(
receiveValue:
receiveCompletion:
)
[weak object]
switchLatest() switchToLatest()
take(_:) prefix(_:)
take(for:scheduler:) 沒有
可以用以下方式解決:
prefix(untilOutputFrom:
Timer.publish(
every: <time>,
on: <scheduler> )
.autoconnect()
.prefix(1)
)

?? replacement in CombineExt
take(until:) prefix(untilOutputFrom:)
take(until:behavior:) prefix(while:)的相反條件來替代,但沒有 behavior參數(shù)功能了
take(while:behavior:) prefix(while:),但沒有 behavior參數(shù)功能了
takeLast(_:) 沒有
最接近的方案:
reduce([]) { 0 + [1] }
.flatMap { $0.suffix(<count>).publisher
}
throttle(
_:
latest:
scheduler:
)
throttle(
for:
scheduler:
latest:
)
timeout(
_:
other:
scheduler:
)
最接近的方案:
timeout(
_:
scheduler:
options:
customError:
)

然后再 catch(_:), 用map轉(zhuǎn)到其他publisher上
timeout(_:scheduler:) timeout(
_:
scheduler:
options:
customError:
)
toArray() collect()
window(
timeSpan:
count:
scheduler:
)
collect(
.byTime(<scheduler>, <time>)
)


collect(
.byTimeOrCount(
<scheduler>,
<time>,
<count>
)
)
withLatestFrom(_:) 沒有
?? replacement in CombineExt
withLatestFrom(
_:
resultSelector:
)
沒有
?? replacement in CombineExt
withUnretained(_:) 最接近的方案:
compactMap { [weak object] value in
object.map { ($0, value) }
}
withUnretained(
_:
resultSelector:
)
最接近的方案:
compactMap { [weak object] value in
object.map {
resultSelector($0, value)
}
}

總結(jié)一下

對我們來說,總是要調(diào)用 setFailureType(to:)eraseToAnyPublisher() 感覺不舒坦,除此之外,我們還是覺得Combine 很棒的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容