AbemaTVでiOSアプリ開発をしておりますshoheiyokoyamaです。
今回は、AbemaTV Advent Calendar 2017の20日目の記事を担当させていただきます。
RxSwift
RxSwift
はReactive programingを実現するためのSwift製フレームワークで、AbemaTVのiOSアプリでメインフレームワークとして使用されています。Reactive Programmingや、それに関連したObserverパターンについては、過去に記事でまとめたものがあるのでこちらで雰囲気だけでも掴んでいただければと思います。
さて、タイトルでTraitという言葉がでてきましたが、RxSwift
はTraitという様々なユースケースで使用できるObservable
のラッパーを提供しています。iOSのcocoaフレームワーク用にRxCocoa traits
というものもありますが、読者のみなさんが眠くなってしまう危険もありますので、今回はRxSwift traits
の説明だけにとどめたいと思います。
RxCocoa traits
のDriver
に関しては、2日前に @inamiy さんがRxSwift.Driver についての個人的見解という素晴らしい記事を書いているので、ご覧いただければと思います。
RxSwift Trait
今回は以下3つのTraitについて触れたいと思います。
- Single
- Completable
- Maybe
それぞれのTraitを説明する前に、RxSwift traits
についてもう少し説明します。
RxSwiftのドキュメントをみてみると、Traitは簡単なObservable
のラッパーで、read-onlyなプロパティという説明があります。
また、RxCocoa traits
と異なり、副作用がないObservable
です。
struct Single<Element> {
let source: Observable<Element>
}
しかしコードを追ってみると、正確にはtypealias
で実体はPrimitiveSequence
であることがわかります。
/// Represents a push style sequence containing 1 element.
public typealias Single<Element> = PrimitiveSequence<SingleTrait, Element>
/// Represents a push style sequence containing 0 elements.
public typealias Completable = PrimitiveSequence<CompletableTrait, Swift.Never>
/// Represents a push style sequence containing 0 or 1 element.
public typealias Maybe<Element> = PrimitiveSequence<MaybeTrait, Element>
このPrimitiveSequence
はGenirics Type
としてTrait
とElement
をもつ構造体で、TraitType
によって振る舞いを変える設計となっています。
/// Observable sequences containing 0 or 1 element.
public struct PrimitiveSequence<Trait, Element> {
let source: Observable<Element>
init(raw: Observable<Element>) {
self.source = raw
}
}
extension PrimitiveSequence: PrimitiveSequenceType {
/// Additional constraints
public typealias TraitType = Trait
/// Sequence element type
public typealias ElementType = Element
...
extension PrimitiveSequenceType where TraitType == SingleTrait {
public static func create(subscribe: @escaping (@escaping SingleObserver) -> Disposable) -> Single<ElementType> {
extension PrimitiveSequenceType where Self.ElementType == Never, Self.TraitType == RxSwift.CompletableTrait {
public static func create(subscribe: @escaping (@escaping PrimitiveSequenceType.CompletableObserver) -> Disposable) -> RxSwift.PrimitiveSequence<Self.TraitType, Self.ElementType>
public extension PrimitiveSequenceType where TraitType == MaybeTrait {
public static func create(subscribe: @escaping (@escaping MaybeObserver) -> Disposable) -> PrimitiveSequence<TraitType, ElementType>
Ruby
やPython
などのTraitのような実装パターンですね。RxSwift Trait
の名前もここからきてるんでしょうか。
それぞれのTraitでcreateが実装されてますが、これはReactiveX
で定義されているOperator
で、Observable
のファクトリーメソッドです。API Design Guidelinesで記載があるようにSwiftのファクトリーメソッドの命名ではmake-prefix
が推奨されてますが、あくまでここはReactiveX
の慣習に習ってるようです。
ドキュメント:ReactiveX / create
標準のcreate operatorをは、カスタムのObservable
を生成することができ、onNext
, onError
, onCompleted
を自由に呼び出すことができます。
func customObservable() -> Observable<Element> {
return Observable.create { observer in
observer.onNext(element)
observer.onError(error)
observer.onCompleted()
return Disposables.create()
}
}
Single
A Single is a variation of Observable that, instead of emitting a series of elements, is always guaranteed to emit either a single element or an error. Traits (formerly Units)より一部抜粋
Singleは一回のみElementかErrorを送信することが保証されているObservable
です。
一回イベントを送信すると、disposeされるようになってます。
var singleObservable: Single<Element> {
return Single.create { single in
fetchData { data, error in
if let error = error {
single(.error(error))
}
single(.success(data))
}
return Disposables.create()
}
}
少し内部のコードを追ってみましょう。
create
の内部ではsubscribe
としてSingleObserver
(=SingleEvent)
を返すようになっており、SingleEvent
ではResult
のようにsuccess
とerror
のみ定義されているため、実装者はonComplete()
を呼び出せないようになっています。
create
の内部では標準のcreate operatorが使用されており、SingleEvent.success
が呼び出されたら即座にon(.completed)
していることがわかります。
public enum SingleEvent<Element> {
/// One and only sequence element is produced. (underlying observable sequence emits: `.next(Element)`, `.completed`)
case success(Element)
/// Sequence terminated with an error. (underlying observable sequence emits: `.error(Error)`)
case error(Swift.Error)
}
public typealias SingleObserver = (SingleEvent<ElementType>) -> ()
public static func create(subscribe: @escaping (@escaping SingleObserver) -> Disposable) -> Single<ElementType> {
let source = Observable<ElementType>.create { observer in
return subscribe { event in
switch event {
case .success(let element):
observer.on(.next(element))
observer.on(.completed) // Itemを送信したらcompletedする
case .error(let error):
observer.on(.error(error))
}
}
}
return PrimitiveSequence(raw: source)
}
observer.on(.error(error))
の場合はobserver.on(.completed)
を呼び出していませんが、on(_ event: Event<E>)
の実装をみてみると、error
が放出された場合にはobserver.on(.completed)
と同様dispose()
が呼び出されることがわかります。
func on(_ event: Event<E>) {
#if DEBUG
_synchronizationTracker.register(synchronizationErrorMessage: .default)
defer { _synchronizationTracker.unregister() }
#endif
switch event {
case .next:
if _isStopped == 1 {
return
}
forwardOn(event)
case .error, .completed: // errorとcompletedの挙動は同じ
if AtomicCompareAndSwap(0, 1, &_isStopped) {
forwardOn(event)
dispose()
}
}
}
ここまで理解できれば、残りのTraitも簡単です。
Completable
Completable
は名前の通り、error
かcompleted
のイベントのみ送信するObservable
で、onNextによるイベントでItemがこないことが保証されています。
var completableObservable: Completable {
return Completable.create { completable in
excuteWork { result in
if case let .error(e) = result {
completable(.error(e))
}
completable(.completed)
}
return Disposables.create {}
}
}
error
かcompleted
が送信されるとObservable
はdisposeされるので、一度のみEvent
を送信することも保証されてますね。
public enum CompletableEvent {
/// Sequence terminated with an error. (underlying observable sequence emits: `.error(Error)`)
case error(Swift.Error)
/// Sequence completed successfully.
case completed
}
...
public static func create(subscribe: @escaping (@escaping CompletableObserver) -> Disposable) -> PrimitiveSequence<TraitType, ElementType> {
let source = Observable<ElementType>.create { observer in
return subscribe { event in
switch event {
case .error(let error):
observer.on(.error(error))
case .completed:
observer.on(.completed)
}
}
}
return PrimitiveSequence(raw: source)
}
Maybe
Maybeは通常のEvent同様next
, error
, completed
を送信することができ、一度のみのEvent
送信が保証されています。いずれかのEventが送信されると即座にObservable
がdispose
されます。
var maybeObservable: Maybe<String> {
return Maybe<String>.create { maybe in
// OR
maybe(.success("Maybe"))
// OR
// maybe(.completed)
// OR
// maybe(.error(error))
return Disposables.create {}
}
}
public enum MaybeEvent<Element> {
/// One and only sequence element is produced. (underlying observable sequence emits: `.next(Element)`, `.completed`)
case success(Element)
/// Sequence terminated with an error. (underlying observable sequence emits: `.error(Error)`)
case error(Swift.Error)
/// Sequence completed successfully.
case completed
}
...
public static func create(subscribe: @escaping (@escaping MaybeObserver) -> Disposable) -> PrimitiveSequence<TraitType, ElementType> {
let source = Observable<ElementType>.create { observer in
return subscribe { event in
switch event {
case .success(let element):
observer.on(.next(element))
observer.on(.completed) // nextのあとにcompletedされる
case .error(let error):
observer.on(.error(error))
case .completed:
observer.on(.completed)
}
}
}
return PrimitiveSequence(raw: source)
}
今回は、RxSwift traits
について簡単に説明しました。
Eventを一度のみだけ受け付けることができる制約は非常に有用で、AbemaTVでもAPIによる値の取得やDBへの書き込みなどは積極的にtraitが使われています。
また、コードを追うことで理解が深まる上に、様々な実装パターンをみることができるのでおすすめです。
簡単ですが、サンプルコードはこちらをご覧ください
今年も残りわずかですが、2017年悔いが残らぬよう、精一杯RxSwift lifeをお楽しみください。