RxSwiftDay 6

sentMessageを使ってviewWillAppearが呼ばれた時にObservableを流してみる+RxSwift3でのextensionの書き方

この投稿は RxSwift Advent Calendar 2016 の6日の記事です。

最近ようやくまともに読める/書けるようになってきたので、小さいですがtipsを。

以前、自分のブログのこちらの記事で書いた内容を、Swift 3、RxSwift 3での書き方に直して紹介しつつ、RxSwift 3でのextensionの書き方を紹介します。


RxCocoaで提供されているsentMessage(_:)

RxCocoaでは、AnyObject(NSObject)に対して、あるSelectorが呼び出された時にObservable<[Any]>を流してあげる関数が用意されています。

public func sentMessage(_ selector: Selector) -> RxSwift.Observable<[Any]>

これを使うと、指定したSelectorが呼び出されたのを起点(トリガー)として、Observableを繋いでいくことができます。

ちなみに、RxSwift2までは、rx_sentMessage(_:)という関数になっていましたが、

Swift3からは、rx.sentMessage(_:)と変更されました。rxというReactive<Self>型をレシーバーとして呼び出します。


viewWillAppear(_:)が呼ばれた時にObservableを流す

class ViewController: UIViewController {

private let disposeBag = DisposeBag()

override func viewDidLoad() {
super.viewDidLoad()
rx.sentMessage(#selector(viewWillAppear(_:)))
.subscribe(onNext: { _ in
print("viewWillAppearが呼ばれた!")
})
.addDisposableTo(disposeBag)
}
}


UIViewControllerのextensionとして実装する


RxSwift 3ではこう書くのが良さそう...!

せっかくなので、UIViewControllerのextensionとして実装してみましょう。

RxSwift 2までは、


旧来までのよくある書き方

extension UIViewController {

var rx_xxxx: Observable<xxx> {
// ...
}
}

みたいに、rx_を付けた書き方が多かったのですが、

RxSwift 3から、 Reactive を拡張するような書き方がなされているので、これに沿って書いていきましょう。


こんな感じで宣言してあげる

extension Reactive where Base: ◯◯


今回は◯◯の部分にUIViewControllerが入ります。

そして、Base: UIViewControllerとすることで、BaseがUIViewControllerに束縛され、 baseという変数がUIViewControllerの型として認識されます。こちらをうまく使っていきましょう。


実装してみる

import RxCocoa

import RxSwift

extension Reactive where Base: UIViewController {
var viewWillAppear: Observable<Void> {
return sentMessage(#selector(base.viewWillAppear(_:)))
.map { _ in () }
.shareReplay(1)
}

var viewDidAppear: Observable<Void> {
return sentMessage(#selector(base.viewDidAppear(_:)))
.map { _ in () }
.shareReplay(1)
}

var viewWillDisappear: Observable<Void> {
return sentMessage(#selector(base.viewWillDisappear(_:)))
.map { _ in () }
.shareReplay(1)
}

var viewDidDisappear: Observable<Void> {
return sentMessage(#selector(base.viewDidDisappear(_:)))
.map { _ in () }
.shareReplay(1)
}
}

それぞれのメソッドのanimatedはあまり使わないと思ったので、Voidで握りつぶすことにしました。

もしかしたら不要かもしれないですが、お行儀よくshareReplayも付けてあげることにします。

これで、先ほどの処理は

class ViewController: UIViewController {

private let disposeBag = DisposeBag()

override func viewDidLoad() {
super.viewDidLoad()
rx.viewWillAppear
.subscribe(onNext: {
print("viewWillAppearが呼ばれた!")
})
.addDisposableTo(disposeBag)
}
}

と書けるようになりました :star2:


1回目だけ/2回目以降にObservableを流すには

実装としてよくあるのが、viewWillAppear(_:)が1回目に呼ばれた時(遷移したときだけで、他の画面から戻ってきた時は呼びたくない)や、

最初は呼ばないで2回目以降呼びたい場合には、以下のようにしてあげます。

rx.viewWillAppear

.take(1)
.subscribe(onNext: {
print("1回目だけ呼ばれる!!")
})
.addDisposableTo(disposeBag)

rx.viewWillAppear
.skip(1)
.subscribe(onNext: {
print("2回目以降呼ばれる!!")
})
.addDisposableTo(disposeBag)

うまくtake(_:)skip(_:)を使ってあげるとこのように、処理を分けることができます。




せっかくなのでもう一つご紹介したいと思います。


おまけ


メソッドが呼ばれた後をトリガーにする

sentMessage(_:)とは別に、そのメソッドが呼び出し終わったときにObservable<[Any]>を流してくれる

methodInvoked(_:)という関数があります。

public func methodInvoked(_ selector: Selector) -> RxSwift.Observable<[Any]>

使い方はsentMessage(_:)と全く同じです。

せっかくなのでこれもextensionの仲間に入れてあげましょう。

import RxCocoa

import RxSwift

extension Reactive where Base: UIViewController {
var viewWillAppearInvoked: Observable<Void> {
return methodInvoked(#selector(base.viewWillAppear(_:)))
.map { _ in () }
.shareReplay(1)
}

var viewDidAppearInvoked: Observable<Void> {
return methodInvoked(#selector(base.viewDidAppear(_:)))
.map { _ in () }
.shareReplay(1)
}

var viewWillDisappearInvoked: Observable<Void> {
return methodInvoked(#selector(base.viewWillDisappear(_:)))
.map { _ in () }
.shareReplay(1)
}

var viewDidDisappearInvoked: Observable<Void> {
return methodInvoked(#selector(base.viewDidDisappear(_:)))
.map { _ in () }
.shareReplay(1)
}
}

これで、

class ViewController: UIViewController {

private let disposeBag = DisposeBag()

override func viewDidLoad() {
super.viewDidLoad()
rx.viewWillAppearInvoked
.subscribe(onNext: {
print("viewWillAppearが呼ばれた後に呼ばれた!")
})
.addDisposableTo(disposeBag)
}
}

こんな感じで使うことができます :star2:


それぞれの呼ばれる順番

以下のようなコードを用意して実行してみます。

import RxSwift

import RxCocoa

class ViewController: UIViewController {

private let disposeBag = DisposeBag()

override func viewDidLoad() {
super.viewDidLoad()

rx.viewWillAppear.subscribe(onNext: {
print("sentMessage!")
}).addDisposableTo(disposeBag)

rx.viewWillAppearInvoked.subscribe(onNext: {
print("sentMessage!")
}).addDisposableTo(disposeBag)
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print(#function)
}

}

// 実行結果

sentMessage!
viewWillAppear
sentMessage!

順番としては、

sentMessage通常の関数呼び出しmethodInvoked

となります。

なので、もしviewWillAppear(_:)での処理が終わった後をトリガーにして何かしたい時は、sentMessageではなくて、methodInvokedを使いましょう。


まとめ

sentMessage(_:)methodInvoked(_:)をうまく活用すると、表現の幅がまた広がるかと思います。

そして、RxSwift3でより洗練された書き方が可能になったので、extensionを書く時も、RxSwiftの書き方に倣ってみると良いと思います。

最後に、今回のextensionをまとめておきます。

import RxCocoa

import RxSwift

extension Reactive where Base: UIViewController {
var viewWillAppear: Observable<Void> {
return sentMessage(#selector(base.viewWillAppear(_:)))
.map { _ in () }
.shareReplay(1)
}

var viewDidAppear: Observable<Void> {
return sentMessage(#selector(base.viewDidAppear(_:)))
.map { _ in () }
.shareReplay(1)
}

var viewWillDisappear: Observable<Void> {
return sentMessage(#selector(base.viewWillDisappear(_:)))
.map { _ in () }
.shareReplay(1)
}

var viewDidDisappear: Observable<Void> {
return sentMessage(#selector(base.viewDidDisappear(_:)))
.map { _ in () }
.shareReplay(1)
}

var viewWillAppearInvoked: Observable<Void> {
return methodInvoked(#selector(base.viewWillAppear(_:)))
.map { _ in () }
.shareReplay(1)
}

var viewDidAppearInvoked: Observable<Void> {
return methodInvoked(#selector(base.viewDidAppear(_:)))
.map { _ in () }
.shareReplay(1)
}

var viewWillDisappearInvoked: Observable<Void> {
return methodInvoked(#selector(base.viewWillDisappear(_:)))
.map { _ in () }
.shareReplay(1)
}

var viewDidDisappearInvoked: Observable<Void> {
return methodInvoked(#selector(base.viewDidDisappear(_:)))
.map { _ in () }
.shareReplay(1)
}
}


参考