この投稿は 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)
}
}
と書けるようになりました
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)
}
}
こんな感じで使うことができます
それぞれの呼ばれる順番
以下のようなコードを用意して実行してみます。
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)
}
}