1. sgr-ksmt

    Posted

    sgr-ksmt
Changes in title
+sentMessageを使ってviewWillAppearが呼ばれた時にObservableを流してみる+RxSwift3でのextensionの書き方
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,315 @@
+[rxswift]: https://github.com/ReactiveX/RxSwift
+
+この投稿は RxSwift Advent Calendar 2016 の6日の記事です。
+
+最近ようやくまともに読める/書けるようになってきたので、小さいですがtipsを。
+
+以前、自分のブログの[こちらの記事](http://blog.sgr-ksmt.org/2016/04/23/viewcontroller_trigger/)で書いた内容を、Swift 3、RxSwift 3での書き方に直して紹介しつつ、RxSwift 3でのextensionの書き方を紹介します。
+
+- [RxSwift][rxswift]
+
+## RxCocoaで提供されている*sentMessage(_:)*
+RxCocoaでは、`AnyObject(NSObject)`に対して、あるSelectorが呼び出された時に`Observable<[Any]>`を流してあげる関数が用意されています。
+
+```swift
+public func sentMessage(_ selector: Selector) -> RxSwift.Observable<[Any]>
+```
+
+これを使うと、指定したSelectorが呼び出されたのを起点(トリガー)として、Observableを繋いでいくことができます。
+
+ちなみに、RxSwift2までは、*rx_sentMessage(_:)*という関数になっていましたが、
+Swift3からは、***rx.sentMessage(_:)***と変更されました。`rx`という`Reactive<Self>`型をレシーバーとして呼び出します。
+
+## *viewWillAppear(_:)*が呼ばれた時にObservableを流す
+
+
+```swift
+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までは、
+
+```swift:旧来までのよくある書き方
+extension UIViewController {
+ var rx_xxxx: Observable<xxx> {
+ // ...
+ }
+}
+```
+
+みたいに、`rx_`を付けた書き方が多かったのですが、
+RxSwift 3から、 `Reactive` を拡張するような書き方がなされているので、これに沿って書いていきましょう。
+
+```swift:こんな感じで宣言してあげる
+extension Reactive where Base: ◯◯
+```
+
+今回は`◯◯`の部分にUIViewControllerが入ります。
+そして、`Base: UIViewController`とすることで、BaseがUIViewControllerに束縛され、 `base`という変数が`UIViewController`の型として認識されます。こちらをうまく使っていきましょう。
+
+### 実装してみる
+
+```swift
+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`も付けてあげることにします。
+
+これで、先ほどの処理は
+
+```swift
+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回目以降呼びたい場合には、以下のようにしてあげます。
+
+```swift
+rx.viewWillAppear
+ .take(1)
+ .subscribe(onNext: {
+ print("1回目だけ呼ばれる!!")
+ })
+ .addDisposableTo(disposeBag)
+
+rx.viewWillAppear
+ .skip(1)
+ .subscribe(onNext: {
+ print("2回目以降呼ばれる!!")
+ })
+ .addDisposableTo(disposeBag)
+```
+
+うまく`take(_:)`、`skip(_:)`を使ってあげるとこのように、処理を分けることができます。
+
+<br />
+せっかくなのでもう一つご紹介したいと思います。
+
+## おまけ
+### メソッドが呼ばれた後をトリガーにする
+*sentMessage(_:)*とは別に、そのメソッドが**呼び出し終わった**ときに`Observable<[Any>`を流してくれる
+*methodInvoked(_:)*という関数があります。
+
+```swift
+public func methodInvoked(_ selector: Selector) -> RxSwift.Observable<[Any]>
+```
+
+使い方は*sentMessage(_:)*と全く同じです。
+せっかくなのでこれもextensionの仲間に入れてあげましょう。
+
+```swift
+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)
+ }
+}
+```
+
+これで、
+
+```swift
+class ViewController: UIViewController {
+
+ private let disposeBag = DisposeBag()
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ rx.viewWillAppearInvoked
+ .subscribe(onNext: {
+ print("viewWillAppearが呼ばれた後に呼ばれた!")
+ })
+ .addDisposableTo(disposeBag)
+ }
+}
+```
+
+こんな感じで使うことができます :star2:
+
+### それぞれの呼ばれる順番
+
+以下のようなコードを用意して実行してみます。
+
+```swift
+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)
+ }
+
+}
+```
+
+```swift
+// 実行結果
+sentMessage!
+viewWillAppear
+sentMessage!
+```
+
+順番としては、
+`sentMessage`→`通常の関数呼び出し`→`methodInvoked`
+となります。
+なので、もし*viewWillAppear(_:)*での処理が終わった後をトリガーにして何かしたい時は、`sentMessage`ではなくて、`methodInvoked`を使いましょう。
+
+## まとめ
+*sentMessage(_:)*、*methodInvoked(_:)*をうまく活用すると、表現の幅がまた広がるかと思います。
+そして、RxSwift3でより洗練された書き方が可能になったので、extensionを書く時も、RxSwiftの書き方に倣ってみると良いと思います。
+最後に、今回のextensionをまとめておきます。
+
+```swift
+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)
+ }
+}
+```
+
+## 参考
+- [RxSwift][rxswift]