【Swift】RxSwift勉強してみたPart6

#はじめに
[前回](https://qiita.com/REON/items/1f6f8af79241b51158bd)
今回はWKWebViewを使った以下のような簡単なアプリを作ります。
![スクリーンショット 2021-04-12 0.43.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/954451/60bdb442-5baf-79ab-d055-a7f32a98fa67.png)

#GitHub
WebViewRxSwiftフォルダにあります

https://github.com/0429oonishi/RxSwifts

#実装

```swift
import UIKit
import RxSwift
import RxCocoa
import RxOptional
import WebKit

final class WKWebViewController: UIViewController {

    @IBOutlet private weak var webView: WKWebView!
    @IBOutlet private weak var progressView: UIProgressView!
    
    private let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupWebView()
        
    }
    
    private func setupWebView() {
        let loadingObservable = webView.rx.observe(Bool.self, "loading")
            .filterNil()
            .share()
        
        loadingObservable
            .map { return !$0 }
            .bind(to: progressView.rx.isHidden)
            .disposed(by: disposeBag)
        
        loadingObservable
            .bind(to: UIApplication.shared.rx.isNetworkActivityIndicatorVisible)
            .disposed(by: disposeBag)
        
        loadingObservable
            .map { [weak self] _ in
                return self?.webView.title
            }
            .bind(to: navigationItem.rx.title)
            .disposed(by: disposeBag)
        
        webView.rx.observe(Double.self, "estimatedProgress")
            .filterNil()
            .map { return Float($0) }
            .bind(to: progressView.rx.progress)
            .disposed(by: disposeBag)
        
        let url = URL(string: "https://www.google.com/")
        let urlRequest = URLRequest(url: url!)
        webView.load(urlRequest)
    }    

}
```

#解説

###filterNil()
RxOptionalライブラリを利用することで使えるようになります。
nilの場合は値を流さず、nilでない場合はアンラップして値をながすオペレーターです。

RxOptionalを使わない場合

```swift
Observable<String?>
    .of("1", nil, "3")
    .filter { $0 != nil }
    .map { $0! }
    .subscribe { print($0) }
```

RxOptionalを使う場合

```swift
Observable<String?>
    .of("1", nil, "3")
    .filterNil()
    .subscribe { print($0) }
```

###share()
ColdなObservableをHotなObservableに変換するオペレーター

以下のコードはtextFieldへのテキスト入力を監視し、ストリームの途中で値を加工して複数のlabelへbindしています。

```swift
let text = textField.rx.text
    .map { text -> String in
        print("call")
    }

text
    .bind(to: label.rx.text)
    .disposed(by: disposeBag)

text
    .bind(to: label2.rx.text)
    .disposed(by: disposeBag)

text
    .bind(to: label3.rx.text)
    .disposed(by: disposeBag)
```

ここで、textFieldに`123`と入力すると、`"call"`は９回呼ばれます。値を入力するたびにmapが3回呼ばれるためです。これは、データーベースアクセスするもの、通信処理が発生するものでは好ましくありません。

なぜこの現象が起こるのかをみていきたいと思います。

`textField.rx.text`は`ControlProperty<String?>`として定義されています。
`ControlProperty`はUI要素のプロパティで使われ、メインスレッドで値が購読されることが保証されているColdなObservableです。
ColdなObservableとは、subscribe(bind)した時点で読み込まれ、複数回subscribeするとその都度ストリームが生成されるという仕組みです。
今回は3回subscribe(bind)したので、3個のストリームが生成されます。すると、値が変更されたときにオペレーターが3回実行されてしまいます。
これを解決するには複数回購読してもオペレーターを一回実行するだけで済ませれば良さそうです。つまり、ColdなObservableをHotなObservableに変換します。そのためにshare()と言うオペレーターを使います。

```swift
let text = textField.rx.text
    .map { text -> String in
        print("call")
    }
    .share()

text
    .bind(to: label.rx.text)
    .disposed(by: disposeBag)

text
    .bind(to: label2.rx.text)
    .disposed(by: disposeBag)

text
    .bind(to: label3.rx.text)
    .disposed(by: disposeBag)
```

こうすることで、123と入力したときに`"call"`が三つだけ出力されていたら成功です。

#おわりに
[次回](https://qiita.com/REON/items/dbceb1ce5a8977734352)
RxSwift少しずつわかってきました。