2016/08/20に開催されたiOSDCに参加してきた。浜松市からの移動なので前日はソニックガーデンの自由が丘ワークプレイスに宿泊しての参加。自由が丘→練馬は乗り換え無しで行ける奇跡の立地。楽しく参加できた。スタッフも大勢いた。開催ありがとうございました。

色々感想があるのでそれは別途書くかもしれない。今日は色々聞いた中でもishkawaさんのRxSwiftの発表を見てRxSwiftに興味を持ったことと、会場で質問もさせてもらったのでその部分を試しがてらちょっと検証してみようと思ったので半年ぶりぐらいに投稿する。

今回は発表スライドにもあった例を使ってみることにする。複数のテキストフィールドとボタンがあって、ボタンを押したら2つのフィールドの値を結合してラベルに表示するというもの。

[http://blog.ishkawa.org/talks/2016-08-20-iosdc/#/19]

まずはこの例をそのまま実装することにした。なお、今回はRxSwift 2.6.0を使用した。

import UIKit
import RxSwift
import RxCocoa

class ViewController: UIViewController {
    @IBOutlet weak var textField1: UITextField!
    @IBOutlet weak var textField2: UITextField!
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var button: UIButton!

    let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()
        Observable
            .combineLatest(textField1.rx_text, textField2.rx_text) { "\($0) \($1)" }
            .sample(button.rx_tap)
            .bindTo(label.rx_text)
            .addDisposableTo(disposeBag)
    }
}

動かしてみるともちろん想定した通りに動く。

このようなコードのときに、「combineLatestに渡すclosureはUITextFiledのテキストが変わる度に毎に呼ばれてしまうのでパフォーマンス的によろしくないこともありますよね?」という点を質問した。それに対するishkawaさんの回答はざっくり要約すると「多分毎回呼ばれるのでそういうこともあるでしょう、でもRxSwiftで制限する仕組みもあるはずです」という感じだった。

ということで2点を試してみようと思う。

まずはクロージャはテキスト変更の度に毎回呼ばれるか、という点。適当にprintして試したところ、次のようになった。

https://gyazo.com/b7a0dafa9b66392e3c62fb589d4a560c

やはりクロージャは都度呼ばれているようだ。なのでこの部分で少々重たい処理をしてしまうと操作する人にはもたつくような印象を与えてしまうことになる。

なので次にここを改善する方法を考えてみる。combineLatestしたストリームをsampleすることで最新の1つを使うことにしているところの順序を逆にすることで、テキスト変更の度にcombineLatestのクロージャが呼ばれることがないので、もう少しマシなのかもしれない。つまり、テキストフィールドの更新をsampleして、それらをcombineLatestするといった感じか。

コードにすると、こう。

let field1 = textField1.rx_text.sample(button.rx_tap)
let field2 = textField2.rx_text.sample(button.rx_tap)
Observable
    .combineLatest(field1, field2) {
        print("called")
        return "\($0) \($1)"
    }
    .bindTo(label.rx_text)
    .addDisposableTo(disposeBag)

これならprint("called")のところはボタンをタップしたときにしか呼ばれない。あまり意味のあるサンプルコードではないので適切かどうかもよくわからないが、一応呼ばれる回数は減らせた。

他にもthrottledebounce(throttleの別名)など一定時間内のものを無視するようなオペレーターも存在するけど、今回の例はボタンをタップするその直前に届いているイベントが欲しいので時間で制御するのは適さない。他の要件なら時間制御も良さそうだ。

こういったオペレーターには色々あって、↓のドキュメントに一覧があるので困ったときは読んでみたいと思っている。

http://reactivex.io/documentation/operators.html

というわけで、RxSwiftの世界へ一歩足を踏み入れた話でした。