【Swift 3.2以降】WKWebView の estimatedProgress の値監視に新しい Block ベースの KVO API を使ってみる

## はじめに
先日，**WKWebViewのestimatedProgressが1.0になったら
自然にUIProgressViewが非表示になるように見せる**[^1] という
記事を投稿しました。挙動の違いでモヤモヤ感は残ったものの
fatal ではないのでそのままアプリに移植し，運用していました。

今日，急に思い立って移植したプロジェクトに
Swift Lint を導入し，リファクタリングをしていたら
下記の warning が出ていました。

```
warning: Block Based KVO Violation: Prefer the new block based KVO API with keypaths when using Swift 3.2 or later. (block_based_kvo)
```

> Swift 3.2 以降では keypaths を持つ新しいブロックベースの KVO APIを推奨します。

とのことなので Swift 4 用に書き直して見ました。

## 実装
### 環境
* Xcode 9.2
* Swift 4 (API 自体は Swift 3.2 以上)
* macOS 10.12.6 以降

### サンプルコード
以前の記事のサンプルに変更加えました。
サンプルは下記になります。動作的には同じに見えます。

[サンプルコード(GitHub)](https://github.com/MilanistaDev/WKWebViewProgress)

## これまでの実装
`WKWebView` の `estimatedProgress` の値の変化を監視して
`UIProgressView` に反映する実装部分を抜き出すと下記になります。
一般的な KVO の形で keyPath の文字列比較や
オブザーバの生成・削除などいろいろやることがありました。

```swift:HogeViewController.swift
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    // estimatedProgress を監視
    self.webView.addObserver(self, forKeyPath: ObserveKey.estimatedProgress, options: .new, context: nil)
}

deinit {
    self.webView.navigationDelegate = nil
    self.webView.uiDelegate = nil
    // オブザーバを削除
    self.webView.removeObserver(self, forKeyPath: ObserveKey.estimatedProgress, context: nil)
}

func setUpUI() {
    self.progressView = UIProgressView(frame: CGRect(x: 0.0,
                                                     y: (self.navigationController?.navigationBar.frame.size.height)! - 3.0,
                                                     width: self.view.frame.size.width,
                                                     height: 3.0))
    self.progressView.progressViewStyle = .bar
    self.progressView.progressTintColor = .red
    self.navigationController?.navigationBar.addSubview(self.progressView)
}

// MARK: - KVO
/// WebViewの読み込み時のProgress KVO
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {

    if (keyPath == ObserveKey.estimatedProgress) {
        self.progressView.alpha = 1.0
        // estimatedProgressが変更されたときにプログレスバーの値を変更
        self.progressView.setProgress(Float(self.webView.estimatedProgress), animated: true)

        if (self.webView.estimatedProgress >= 1.0) {
            UIView.animate(withDuration: 0.3,
                           delay: 0.3,
                           options: [.curveEaseOut],
                           animations: { [weak self] in
                            self?.progressView.alpha = 0.0
                }, completion: { _ in
                    self.progressView.setProgress(0.0, animated: false)
            })
        }
    }
}
```

## 新しい KVO API を使ってみる
新しい Block ベースの KVO  API について
warning の文章をそのまま検索してみたところ，
Swift Lint の issue[^2] に上がっていたので，参考に書き直しました。
下記のようになりました。

```swift:HogeViewController.swift
// nil を書くと Redundant Optional Initialization Violation で冗長
var observeLoading: NSKeyValueObservation?

func setUpUI() {
    self.progressView = UIProgressView(frame: CGRect(x: 0.0,
                                                     y: (self.navigationController?.navigationBar.frame.size.height)! - 3.0,
                                                     width: self.view.frame.size.width,
                                                     height: 3.0))
    self.progressView.progressViewStyle = .bar
    self.navigationController?.navigationBar.addSubview(self.progressView)

    // Swift 3.2 以降の KVO
    self.observeKeysFowWebView()
}


func observeKeysFowWebView() {

    // WKWebView の estimatedProgress の値監視(中の処理は同じ)
    self.observeEstimatedProgress = self.webView.observe(\.estimatedProgress, options: [.new], changeHandler: { (webView, change) in
        self.progressView.alpha = 1.0
        // estimatedProgressが変更されたときにプログレスバーの値を変更
        self.progressView.setProgress(Float(change.newValue!), animated: true)

        if (self.webView.estimatedProgress >= 1.0) {
            UIView.animate(withDuration: 0.3,
                           delay: 0.3,
                           options: [.curveEaseOut],
                           animations: { [weak self] in
                            self?.progressView.alpha = 0.0
                }, completion: { _ in
                    self.progressView.setProgress(0.0, animated: false)
            })
        }
    })
}
```

`WKWebView` だと他に `title`，`URL`，`loading` がありますね。
同様にすれば対応可能だと思います。
今回は新しい値だけをみたのですが，
色々対応するなら `options` は `[.new, .old]` の方がいいのかな。

## おわりに
今回は，Swift 3.2 以降で推奨になった Block ベースの KVO について
`WKWebView` の `estimatedProgress` の値監視を例に記事を書きました。
Swift 3.2 から推奨ということでそれ以下のバージョンに対応するなら
これまで通りの KVO でプロパティ監視することになります。

Swift Lint 導入して警告や warning を修正するにあたって，
気づくことも多く，うまく付き合っていけたらなと思います。
ご覧いただきありがとうございました。
理解を深めたいのでコード間違いやご意見ありましたらよろしくお願いいたします。

## 参考

[^1]: [WKWebViewのestimatedProgressが1.0になったら自然にUIProgressViewが非表示になるように見せる](https://qiita.com/MilanistaDev/items/be545a5d6387c1e4a3ac)
[^2]: [[Rule Request] Prefer new block based KVO API in Swift 3.2 or 4 #1714](https://github.com/realm/SwiftLint/issues/1714)