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

[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]