【RxSwift】Rx の Extension を作成するパターン色々

## 実現したいこと
- `Binder` や `Observable` を作成してコードの整理を行いたい


## 環境
- ReactiveCocoa/ReactiveSwift 6.7.0
- ReactiveX/RxSwift 6.2.0
- Xcode Version 13.1 (13A1030d)

## 基本形: `.rx.` の形で呼び出せるようにする
- 元を辿ると `.rx.` でアクセスしているのは `Reactive<Base>`
- 定義上 `.rx.` でアクセス可能な `Reactive<Base>` の `Base` は `NSObject` になる
- 拡張したい `class` を `Base` として持つ `Reactive<Base>` を拡張することで `.rx.` で呼び出せるようにする

```SampleClass+Rx.swift
// MARK: - SampleClass

extension Reactive where Base: SampleClass {
    
    // ここに何か .rx.◯◯ とアクセスしたいプロパティを定義する.
}
```

## パターン1: `Binder` で受け口を作る
- 通常のプロパティに関しては RxSwift ライブラリ側で自動で `Binder` が作られる
    - 例: `isEnabled`, `isHidden` など
    - 内部的には KeyPath Member Lookup を利用してプロパティごとに `Binder` を作成している
- ユースケースとして基礎的なクラス(`UIButton` など)に対して、元の値を触らず実効的には `didSet` を行うような挙動が可能

```UILabel+Rx.swift
// MARK: - UILabel

extension Reactive where Base: UILabel {
    
    /// 赤字による文字更新.
    var textWithRed: Binder<String?> {
        
        return Binder<String?>(self.base) { (label: UILabel, value: String?) in
            
            label.text = value
            label.textColor = .red
        }
    }
}
```

## パターン2: イベントが呼び出されたタイミングを知りたい
- `@objc` となっているイベントは呼び出されたタイミングを取得可能
- ユースケースとしては画面のライフサイクルに関連するイベントが呼び出された際にそれに応じた処理を行うなど

```UIViewController+Rx.swift
// MARK: - UIViewController

extension Reactive Base: UIViewController {

    /// view の表示直前を表す Observable
    var viewWillAppear: Observable<Void> {
        
        return self.sentMessage(#selector(base.viewWillAppear(_:)))
            .map { _ -> Void in return () }
            .share(replay: 1)
    }
}
```
- `Reactive.sentMessage(#selector)` の返却値は `Observable<[Any]>`
- 今回はタイミングが知りたいだけなので `map` で `Void` に変換している
- `.share(replay: 1)` については [こちらの記事](https://qiita.com/kazu0620/items/bde4a65e82a10bd33f88) が詳しいです
    - ざっくり言うと複数購読されても余計なストリームを流さないようにしている

## パターン3: クロージャによる非同期処理を Rx に変換する

- 以下のようなクロージャによる非同期処理を持つクラスがあった場合に、処理を Rx 化したい

```SampleClass.swift
// MARK: - SampleClass

class SampleClass: NSObject {
    
    func fetchData(url: URL, completion: @escaping ((Result<Data, Error>) -> Void)) {
        
        // テストのため3秒後に .success を返却する.
        DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
            
            completion(.success(Data()))
        }
    }
}
```

```SampleClass+Rx.swift
// MARK: - SampleClass

extension Reactive where Base: SampleClass {
    
    func fetchData(url: URL) -> Observable<Result<Data, Error>> {
        
        return Observable<Result<Data, Error>>.create { [weak base] (observer: AnyObserver<Result<Data, Error>>) in

            base?.fetchData(url: url, completion: { (result: Result<Data, Error>) in
                
                observer.onNext(result)
                observer.onCompleted()
            })
            
            return Disposables.create()
        }
    }
}
```


## パターン4: デリゲートメソッドが発火したタイミングが知りたい

- 以下のようなデリゲートを持つクラスがあった場合に、デリゲートのメソッドが呼ばれたタイミングを知りたい


```SampleClass.swift
// MARK: - SampleClass

open class SampleClass: NSObject {

    weak var delegate: SampleClassDelegate?
    
    public func processA() {
        
        self.delegate?.processA?()
    }
    
    public func processB(by value: String) -> String? {
        
        return self.delegate?.processB(by: value)
    }
}


// MARK: - SampleClassDelegate

@objc public protocol SampleClassDelegate: AnyObject {

    @objc optional func processA()

    func processB(by: String) -> String?
}
```

### 手順1: `Rx**DelegateProxy` を作成
- `DelegateProxyType` と `SampleClassDelegate` を準拠させる
- `SampleClassDelegate` で `optional` として定義されているかどうかで扱いが異なるため注意
- 同様に返却値が `Void` かどうかでも扱いが異なるため注意

```RxSampleClassDelegateProxy.swift
// MARK: - RxSampleClassDelegateProxy

public class RxSampleClassDelegateProxy: DelegateProxy<SampleClass, SampleClassDelegate> {
    
    // processB のイベント発火検知用 Subject
    internal lazy var processBDidInvokedSubject = PublishSubject<String?>()
    
    // ParentObject および Delegate は DelegateProxy で定義されている.
    // 今回は SampleClass が ParentObject 、 SampleClassDelegate が Delegate にあたる.
    public init(by value: ParentObject) {
        
        super.init(parentObject: value, delegateProxy: RxSampleClassDelegateProxy.self)
    }
    
    // Proxy が破棄されるタイミングで complete をイベントとして流す.
    deinit {
        
        self.processBDidInvokedSubject.onCompleted()
    }
    
    // DelegateProxy の Subclass である RxSampleClassDelegateProxy を factory に登録する.（実装必須）
    public static func registerKnownImplementations() {
        
        self.register { RxSampleClassDelegateProxy(by: $0) }
    }
}


// MARK: - DelegateProxyType

extension RxSampleClassDelegateProxy: DelegateProxyType {
    
    // ParentObject が持つ delegate を返却する.
    public static func currentDelegate(for object: ParentObject) -> Delegate? {
        
        return object.delegate
    }
    
    // ParentObject が持つべき delegate を設定する.
    public static func setCurrentDelegate(_ delegate: Delegate?, to object: ParentObject) {
        
        object.delegate = delegate
    }
}


// MARK: - SampleClassDelegate

extension RxSampleClassDelegateProxy: SampleClassDelegate {

    // delegate の定義上で必須となっているメソッドの定義方法.
    // Proxy を通じて本来の delegate 実装先を発火させる.
    // ここで定義したメソッドは sentMessage / methodInvoked ではイベント検出できない.
    // また、返却値のあるメソッドは sentMessage / methodInvoked が呼ばれないためここでイベントを流す必要がある.
    public func processB(by value: String) -> String? {
        
        let result: String? = _forwardToDelegate?.processB(by: value)
        self.processBDidInvokedSubject.onNext(result)

        return result
    }
}
```

### 手順2: `Reactive` の extension を作成
- 以下の２パターン
    - `sentMessage` / `methodInvoked` を利用する
    - `Rx**DelegateProxy` で定義しておいた `PublishSubject` を利用して `Observable` を作成

```SampleClass+Rx.swift
// MARK: - Reactive

extension Reactive where Base: SampleClass {
    
    public var delegateProxy: RxSampleClassDelegateProxy {
        
        RxSampleClassDelegateProxy.proxy(for: self.base)
    }
    
    // void を返却する optional のメソッドは sentMessage / methodInvoked が利用可能.
    public var processAWillCalled: Observable<Void> {
        
        return self.delegateProxy
            .sentMessage(#selector(SampleClassDelegate.processA))
            .map { _ -> Void in return () }
            .share(replay: 1)
    }
    
    public var processADidCalled: Observable<Void> {
        
        return self.delegateProxy
            .methodInvoked(#selector(SampleClassDelegate.processA))
            .map { _ -> Void in return () }
            .share(replay: 1)
    }
    
    public var processBDidCalled: Observable<String?> {
        
        return self.delegateProxy.processBDidInvokedSubject.asObservable()
    }
    
    // Rx**DelegateProxy で定義した delegate のメソッドは sentMessage / methodInvoked が呼ばれない.
    // 以下の実装は呼ばれない.
    /*
    public var processBWillCalled: Observable<Void> {
        
        return self.delegateProxy
            .sentMessage(#selector(SampleClassDelegate.processB(by:)))
            .map { _ -> Void in return () }
            .share(replay: 1)
    }
    
    public var processBDidCalled: Observable<Void> {
        
        return self.delegateProxy
            .methodInvoked(#selector(SampleClassDelegate.processB(by:)))
            .map { _ -> Void in return () }
            .share(replay: 1)
    }
    */
}
```

### 使用例

```swift
// MARK: - TestViewController

class TestViewController: UIViewController {

    override func viewDidLoad() {
        
        super.viewDidLoad()
        
        self.setupRx()

        self.test()
    }


    // MARK: - Private
    
    private let disposeBag = DisposeBag()

    private let sampleClass = SampleClass()
    
    private func setupRx() {

         self.sampleClass.rx.processAWillCalled
             .subscribe(onNext: {

                 print("Process A Will Called")
             })
             .disposed(by: self.disposeBag)

         self.sampleClass.rx.processADidCalled
             .subscribe(onNext: {

                 print("Process A Did Called")
             })
             .disposed(by: self.disposeBag)

         self.sampleClass.rx.processBDidCalled
             .subscribe(onNext: {

                 print("Process B Did Called")
             })
             .disposed(by: self.disposeBag)
    }

    private func test() {
    
         // SampleClassDelegate を実装しなくても rx にストリームを流せる.
         self.sampleClass().processA()
         _ = self.sampleClass().processB()
    }
}
```

### 注意点
- `SampleClassDelegate` メソッドを実装しなくてもイベントは検出可能になるが、あくまで「串(Proxy)」を刺してイベントの発火を検出しているだけなので扱いには注意

### 不明点

DelegateProxy.swift の `sentMessage` の定義箇所に以下のコメントがある

>Only methods that have `void` return value can be observed using this method because those methods are used as a notification mechanism. It doesn't matter if they are optional or not.

メソッドの返却値が `void` であれば `optional` であっても `sentMessage` が使えるような書き振りだが、
実際 `optional` ではないメソッドは `Rx**DelegateProxy` に定義する必要があり、
そのため `sentMessage` でイベントを取得できなかった
なにか別の実装方法があるのかもしれませんが、ちょっと思いつきませんでした

## 参考
- [GeolocationSampleから学ぶdelegateのRx対応](https://grandbig.github.io/blog/2018/10/06/rx-delegate/)
- [[Swift]Dynamic Member LookupからSwift5.1で追加されたKeyPath Member Lookupまで](https://dev.classmethod.jp/articles/playground-dynamic-member-lookup-dynamiccallable/)
- [[RxSwift] shareReplayをちゃんと書いてお行儀良くストリームを購読しよう](https://qiita.com/kazu0620/items/bde4a65e82a10bd33f88)