もくもくと勉強する会(如法会 その2)に参加しています。広島の勉強会ですが、長野の自宅から参加です。
題材はなんでも良いらしいので、前から気になっていたRxSwiftについて学んでみました。
実は以前にもRxSwiftをアプリに組み入れようとしてみたのですが、いざ取り組んでみようとしたらXcodeを前に「で、自分の場合は何から手を付けたら??」と固まってしまった諦めた経験があるので、この機に基本からやり直すことに決めた次第です。
#やったこと・わかったこと
ArrayやMapをObservable()
にする
RxSwiftの土俵に乗るためには、まず何より対象をストリームに乗せるべく、Observable
化しなければいけません。
ArrayやMapのようなSequenceTypeは、Array
かSequenceType
の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)>
Array
、SequenceType
の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)
と配列化していました。Map
にtoObservable()
を呼ぶと、タプルの配列に変化するようです。
// 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
が渡されていますが、代わりにConcurrentMainScheduler
やSerialDispatchQueueScheduler
も指定できるようです。このへんの使い分け方はまだわかりません。
いちおう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の基本からアプリに組み込む時の実践的な内容までが書かれた、良書になっています。
お近くの本屋で見かけたら、ぜひ手にとってみてはいかがでしょうか。払った値段を超える知識を得られると思います。私もずっと手元に置いておき、調べるのに使っています。