72
37

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

RxSwiftAdvent Calendar 2016

Day 6

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

Last updated at Posted at 2016-12-05

この投稿は 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)
    }
}

参考

72
37
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
72
37

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?