Swift
RxSwift

RxSwiftに取り組み始めた

More than 1 year has passed since last update.

もくもくと勉強する会(如法会 その2)に参加しています。広島の勉強会ですが、長野の自宅から参加です。

題材はなんでも良いらしいので、前から気になっていたRxSwiftについて学んでみました。

実は以前にもRxSwiftをアプリに組み入れようとしてみたのですが、いざ取り組んでみようとしたらXcodeを前に「で、自分の場合は何から手を付けたら??」と固まってしまった諦めた経験があるので、この機に基本からやり直すことに決めた次第です。


やったこと・わかったこと


ArrayやMapをObservable()にする

RxSwiftの土俵に乗るためには、まず何より対象をストリームに乗せるべく、Observable化しなければいけません。

ArrayやMapのようなSequenceTypeは、ArraySequenceTypeのextensionで定義されたtoObservable()メソッドによって、Observable化できます。

// Arrayのextensionが使われる

print("\([1,2,3,4,5,6].toObservable())")
// → RxSwift.Sequence<Swift.Int> Array

// SequenceTypeのextensionが使われる
print("\((0...100).toObservable())")
// → RxSwift.Sequence<Swift.Int>

print("\([0:1, 2:3, 4:5, 6:7].toObservable())")
// → RxSwift.Sequence<(Swift.Int, Swift.Int)>

ArraySequenceTypeの2種類のtoObservable()は、どちらもObservable+Creation.swiftに実装があり、Sequenceクラスをインスタンス化して返すようになっていました。下はArray版のコードです。

// extension Array

@warn_unused_result(message="http://git.io/rxs.uo")
public func toObservable(scheduler: ImmediateSchedulerType? = nil) -> Observable<Generator.Element> {
return Sequence(elements: self, scheduler: scheduler)
}

他方のSequenceTypeもほとんど同じで、上のコードでelements引数に対してArray(self)と配列化していました。MaptoObservable()を呼ぶと、タプルの配列に変化するようです。

// extension Array

@warn_unused_result(message="http://git.io/rxs.uo")
public func toObservable(scheduler: ImmediateSchedulerType? = nil) -> Observable<Generator.Element> {
return Sequence(elements: Array(self), scheduler: scheduler)
}

このschedulerは、デフォルト引数によってnilが渡されていますが、代わりにConcurrentMainSchedulerSerialDispatchQueueSchedulerも指定できるようです。このへんの使い分け方はまだわかりません。

いちおうnilを渡したときの処理を追ってみると、インスタンス化の時点では単にメンバー変数にそのまま渡しているものの、Sequenceクラスのsubscribe()メソッド内でスケジューラの有無による別処理が走ることになっていました。コメントにある通り、その場で実行することで、わざわざSequenceSinkにご登場いただかなくても済むようになっています。

// Sequence.swift

class Sequence<E> : Producer<E> {
:
override func subscribe<O : ObserverType where O.E == E>(observer: O) -> Disposable {
// optimized version without scheduler
guard _scheduler != nil else { // ★nilの場合はここに入る!
for element in _elements {
observer.on(.Next(element))
}

observer.on(.Completed)
return NopDisposable.instance
}

let sink = SequenceSink(parent: self, observer: observer)
sink.disposable = sink.run()
return sink
}
}


要素を順に読んでいく

Observable化したあとは、RxSwiftの言葉で話します。ArrayやMapの要素を1つずつ読んでいくにはObservableTypeのextension、subscribe()メソッドを使います。先ほどのSequence.swiftファイルで出てきました。

[1,2,3,4,5,6].toObservable()

.subscribe { event in
print("\(event)")
// Event(1) → Event(2) → ... → Completed
}
.addDisposableTo(DisposeBag())

クロージャに届くのはenumのEventで、それぞれの値がenumにラップされているものです。この型はとてもシンプルなもので、デバッグプリントなどを除けば以下で表現できる簡単な型です。

public enum Event<Element> : CustomDebugStringConvertible {

case Next(Element)
case Error(ErrorType)
case Completed
}

上のコードは、実行するとEvent(1) → Event(2) → ... → Completedというように1行ずつ出力されて終わります。最後にCompletedが届いているので、きちんとswitchなどでパターンマッチングしてあげないといけないですね。


後片付け

Rxにおける後片付けは、DisposeBagオブジェクトにaddDisposableTo()することです。それにより、Completedが届いたあとに実行中に確保していたリソースは解放され、一連のストリームが終了します。

DisposeBag()DisposeBag.swiftファイルで定義されています。各Observable(正確にはDisposableに従うオブジェクト)のextensionもここで実装してあり、addDisposable(bag)メソッドを呼ぶことで、bagが持つ配列に格納するようになっています。

extension Disposable {

public func addDisposableTo(bag: DisposeBag) {
bag.addDisposable(self)
}
}

DisposeBagを呼んでみたところ、面白いことに生きている間は何もしないことがわかりました。デイニシャライザで仕事をしていて、これまでaddしてきたDisposableなオブジェクトを配列から取り除き、そのdispose()メソッドを呼ぶようになっていました。


次にしたいこと

今回は、ここまでです。ArrayやMapをObservableにする方法と、それをsubscribeする方法、そして、最後にDispoaseBag()にて後片付けされることがわかりました。

次回の如法会に参加する場合、flatMapやmapについて知見を深められたらと思います。

以上です。


余談:書籍のお知らせ

最後に、知人の書いた本をちょっとだけですが宣伝です。

以前、「上を目指すプログラマーのためのiPhoneアプリ開発テクニック iOS 7編」などで本を一緒に書いた西方さんが、新しくCore Data+Swiftをテーマにした本「Swift+Core DataによるiOSアプリプログラミング」を出しました。

献本いただいたので私も読んでみたのですが、Core Dataの基本からアプリに組み込む時の実践的な内容までが書かれた、良書になっています。

お近くの本屋で見かけたら、ぜひ手にとってみてはいかがでしょうか。払った値段を超える知識を得られると思います。私もずっと手元に置いておき、調べるのに使っています。