SwiftでModel の変更を監視

MVCでImageを検索してみると、想像している以上にいろいろな、MVCの図に遭遇します。どの図がいいとかの話は置いておくとして、Model に変更があった場合、何らかの方法で View または ViewController はその変更を検知して表示に反映させたいものです。

<div>
<img width="200" alt="wikipedia" src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/a0/MVC-Process.svg/400px-MVC-Process.svg.png"/>

<img width="300" src="https://manojjaggavarapu.files.wordpress.com/2012/05/mvcbase.png">
</div>

<div>
<img width="350" src="http://www.patricksoftwareblog.com/wp-content/uploads/2015/07/Model-View-Controller-High-Level-Diagram.png">
<img width="300" alt="SAP" src="https://help.sap.com/static/saphelp_uiaddon10/en/91/f233476f4d1014b6dd926db0e91070/loio1eb216151b1b41f1979b7b6c969670df_LowRes.png"/>
</div>

今回は、このモデルの変更の通知を簡単にする為に、軽量のユーティリティコードを開発したので紹介したいと思います。

# ZObservable

![swift](https://img.shields.io/badge/swift-3.0-orange.svg) ![license](https://img.shields.io/badge/license-MIT-yellow.svg)

`ZObservable` のコードは以下の github より入手可能になっています。

https://github.com/codelynx/ZObservable

## ZObservable と ZObserver

`ZObservable` は監視される側の `ZObservable` と、監視する側 `ZObserver` の基本構成としています。どちらも `protocol` ですが、監視する側も監視される側もオブジェクトでなくてはなりません。

まず監視される側の典型的なコードは以下のようになります。この例では、`image` に変更があった場合は、`self.observableDidChange()` を呼び出します。`image` の他にも監視対象下に置きたいプロパティなどがあれば、変更があった際に `self.observableDidChange()` を呼びます。そうです、監視と言いながら、通知は監視される側の自己申告です。また、`KVO` と違い、どのオブジェクトのどのプロパティに変更があったという監視ではなく、あるオブジェクトの何かが変わったという情報の粒度での監視を行います。再表示が必要である事を監視する程度であれば、この程度の情報の粒度で十分であるかと思います。

```.swift
class MyObservable: ZObservable { // 監視される側
	var image: UIImage? {
		didSet {
			self.observableDidChange()
		}
	}
}
```

そして、監視する側は `func observableDidChange(_ observable: ZObservable)` を実装する必要があります。監視対象のオブジェクトに変更があった場合はこのメソッドが呼ばれるからです。また、Observer は複数の Observable を監視対象化におく事ができるので、必要に応じて、switch 文などで、タイプ別、または インスタンス別に処理を切り分ける事ができます。


```.swift
class MyObserver: ZObserver { // 監視する側
	func observableDidChange(_ observable: ZObservable) {
		switch observable {
		case let object as MyObservable:
			print("\(object) did change.")
		default:
			break
		}
	}
}
```

これで監視する側と監視される側のクラスの準備はできました。次はこの二つをつなぎ合わせます。

```.swift
let observable = MyObservable()
let observer = MyObserver()
observable.addObserver(observer)
```

この状態で、Observable の監視の対象になっているプロパティを変更すると、Observer側 の `observableDidChange()` が呼ばれます。 

```.swift
observable.image = UIImage(named: "...") // "MyObservable did change."
```

そして監視不要になった場合は、Observable から `removeObserver()` で監視対象から外す事ができますが、監視する側も監視される側も弱参照(weak reference)で登録されている為、どちらか、もしくは、その両方のオブジェクトがリリースされた場合は自動的に監視対象から外れ、通知はされなくなります。

```.swift
observable.removeObserver(observer)
```

次のようなコードで確認する事ができます。`observer` がスコープを抜けた時点以降通知されていない事に注意ください。

```.swift
class YourObservable: ZObservable { // 監視される側
	var value: Int = 0 {
		didSet {
			self.observableDidChange()
		}
	}
}

class YourObserver: ZObserver { // 監視する側
	func observableDidChange(_ observable: ZObservable) {
		switch observable {
		case let object as YourObservable:
			print("value did change to `\(object.value)`")
		default:
			break
		}
	}
}

let observable = YourObservable()

do {
	let observer = YourObserver()
	observable.addObserver(observer)
	observable.value = 1 // "value did change to `1`"
}

observable.value = 2 // 通知されない
```

## 課題

`ZObservable` は単なるプロトコルなので protocol extension を使えば、多くのクラスを Observable にする事ができます。例えば　`NSMutableURLRequest` を protocol extension で `ZObservable` にしてしまえば、マニュアルでですが、`observableDidChange()` を呼び出して、通知を行う事ができます。

```.swift
extension NSMutableURLRequest: ZObservable {}
let request = NSMutableURLRequest(url: URL(string: "http://www.apple.com")!)
observer.addObservable(request)

request.setValue("string", forHTTPHeaderField: "custom")
request.observableDidChange() // notify
```
ところが、`NSMutableString`, `NSMutableArray`, `NSMutableDictionary` などの一部のオブジェクトは Mutable と謳っていても、実際に変更を加えると、同一オブジェクトのはずなのに通知が行われなくなります。これは クラスタークラスが原因ではないかと考えていますが、今後の課題となりますので、気をつけてください。

```.swift
extension NSMutableString: ZObservable {}
extension NSMutableArray: ZObservable {}
extension NSMutableDictionary: ZObservable {}

let string = NSMutableString(string: "abc")
let array = NSMutableArray(array: [1, 2, 3])
let dictionary = NSMutableDictionary(dictionary: ["a": 1, "b": 2])

let observer = Observer()
observer.addObservable(string)
observer.addObservable(array)
observer.addObservable(dictionary)

string.observableDidChange() // notify
array.observableDidChange() // notify
dictionary.observableDidChange() // notify

string.append("def")
array.add(4)
dictionary["c"] = 3

string.observableDidChange() // not notify
array.observableDidChange() // not notify
dictionary.observableDidChange() // not notify
```

## その他の方法

その他の方法も考慮される場合は以下のようなリンクが参考になるかもしれません。

* [Key-Value Observing](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html)
* [Using Notification and NotificationCenter](https://developer.apple.com/reference/foundation/nsnotificationcenter)
* [RxSwift](https://github.com/ReactiveX/RxSwift)


## 環境に関する表記

```.log
Xcode Version 8.1 (8B62)
Apple Swift version 3.0.1 (swiftlang-800.0.58.6 clang-800.0.42.1)
```