Xcode
Swift
RxSwift
RxCocoa

メソッドのhookは正しいタイミングで行おう【RxSwift/RxCocoa】

メソッドのhook?

RxCocoaではsentMessagemethodInvokedというメソッドをフックするオペレータがあり、「ある関数が呼ばれたらこの処理をしたい」ということが実現できます。 

sentMessage

ある関数が呼ばれる直前をhookすることができます。

(数値の順番通りに処理されます)

import UIKit
import RxSwift
import RxCocoa

extension UIViewController {
    func doSomething() {
        defer { print("3") }
        print("2")
    }
}

final class ViewController: UIViewController {

    let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()

        rx.sentMessage(#selector(UIViewController.doSomething))
            .subscribe(onNext: { _ in
                print("1")
            })
            .disposed(by: disposeBag)

        doSomething()
        print("4")        
    }    
}

methodInvoked

ある関数の処理が終わった直後をhookすることができます。

(数値の順番通りに処理されます)

import UIKit
import RxSwift
import RxCocoa

extension UIViewController {
    func doSomething() {
        defer { print("2") }
        print("1")
    }
}

final class ViewController: UIViewController {

    let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()

        rx.methodInvoked(#selector(UIViewController.doSomething))
            .subscribe(onNext: { _ in
                print("3")
            })
            .disposed(by: disposeBag)

        doSomething()
        print("4")
    }
}

面白いこと :coffee:

sentMessagemethodInvokedはそれぞれ関数に渡ってくる引数を取得することができます。戻り値がObservable<[Any]>となっているため引数ラベルなどは使えませんが、引数が先頭から何番目かを指定することで引数を取得できます。

つまりどういうことなのか? :thinking:

使い道としては指定した関数内の同期処理が完全に終わってるタイミングを保証できるのがmethodInvokedです。なので、指定した関数内の処理後でないと特定のプロパティが更新されていない可能性があるなどのものは全てmethodInvokedを使います。反対に指定した関数が呼ばれたタイミングで関数内の処理とは無関係に処理したい場合はsentMessageを使います。

:warning:
注意として指定した関数内に非同期処理があった場合にはそれは関数オプジェクトの生存期間と別管理となるため無視されます。sentMessagemethodInvokedの内部実装ではSelectorが呼ばれるタイミングと関数がdeallocatedされたタイミングで通知を行なっているので関数がdeallocatedになっても処理が継続するものについては通常通りcompletion等で対応するしかありません。

参考:
https://github.com/ReactiveX/RxSwift/blob/102424379fb8d6c69b33b95c67504679042f44cc/RxCocoa/Foundation/NSObject%2BRx.swift