二つの NSDictionary を Swift でマージする

あるプロジェクトで結構な量のデータを扱っています。それぞれは手作りのデータなので、`.plist` でデータを用意しています。しかし、あまりにも似たようなデータが多いので、共通のデータと固有のデータを分離できれば、共通部のデータの入力が省力化できるどころか、共通部のデータに変更があった場合でもかなりの省力化が期待できるはずです。

問題は、データにはサブクラス・スーパクラスのような概念がないので、どのように実現すればいいのか考えてみました。そこで、二つの `NSDictionary` が上手くマージできれば、上手く実現できるのではないかと考えました。

* `source` の `NSDictionary` が `destination` の `NSDictionary` のエントリを一つづつ上書きする。
* もし、`destination` 側に同じエントリがない場合はそのままエントリを追加する。
* もし、`destination` 側に同じエントリがあった場合は
    * そのエントリの双方が、`Dictionary`であった場合は、さらにその`Dictionary`をマージする。
    * そのエントリの双方が、`Array`であった場合は、さらにその`Array`をマージする。
    * 双方のエントリが異なる場合を含む、上記二つに該当しない場合は `destination`を`source`で上書きする。
* この上記を再帰的に繰り返す。

クライアントコードのイメージはこんな感じです。

```.swift
let d11 = NSDictionary(dictionary: ["a": 1, "b": 2, "c": 3])
let d12 = NSDictionary(dictionary: ["c": 13, "d": 14, "e": 15])
let d13 = d11 + d12 // ["a": 1, "b": 2, "c": 13, "d": 14, "e": 15]
```

```.swift
let d21 = NSDictionary(dictionary: ["a": ["p": 1, "q": 2, "r": 30], "b": [:])
let d22 = NSDictionary(dictionary: ["a": ["r": 3, "s": 4, "t": 5], "b": ["x": "en", "y": "fr"])
let d23 = d21 + d22 //["a": ["p": 1, "q": 2, "r": 3, "s": 4, "t": 5], "b": ["x": "en", "y": "fr"])
```

```.swift
let d41 = NSDictionary(dictionary: ["a": ["b": ["c": ["d": ["e": [1, 2, 3]]]]]])
let d42 = NSDictionary(dictionary: ["a": ["b": ["c": ["d": ["e": [4, 5, 6]]]]]])
let d43 = d41 + d42 // ["a": ["b": ["c": ["d": ["e": [1, 2, 3, 4, 5, 6]]]]]]
```

もっとも、異なるデータタイプ同士や`Dictionary`でも`Array`でもない`String`や`Number`タイプ同士では、オーバーライドされます。

```.swift
let d31 = NSDictionary(dictionary: ["a": 1, "b": 2, "c": 3, "d": 4])
let d32 = NSDictionary(dictionary: ["a": true, "b": "foo", "c": 3.25])
let d33 = d31 + d32 // ["a": true, "b": "foo", "c": 3.25, "d": 4]
```

`+` オペレータを使うとなんかかっこよくかけたような気がしますね。また `+` オペレータで連結すれば、３階層以上のデータ分離が期待できそうですが、同時にデータの管理手法も問われる事になりそうな予感がします。

さて、`extension`のコード本体はこんな感じになります。

```NSDictionary+merge.swift
import Foundation

extension NSDictionary {

	public static func + (lhs: NSDictionary, rhs: NSDictionary) -> NSDictionary {
		let dictionary = NSMutableDictionary()
		for (key, value) in lhs {
			if let key = key as? NSCopying {
				dictionary[key] = value
			}
		}
		for (key, valueR) in rhs {
			if let key = key as? NSCopying {
				if let valueL = lhs[key] {
					switch (valueL, valueR) {
					case (let dictionaryL as NSDictionary, let dictionaryR as NSDictionary):
						dictionary[key] = dictionaryL + dictionaryR
						break
					case (let arrayL as NSArray, let arrayR as NSArray):
						dictionary[key] = arrayL + arrayR
					default:
						dictionary[key] = valueR
					}
				}
				else {
					dictionary[key] = valueR
				}
			}
		}
		return dictionary
	}

}

extension NSMutableDictionary {

	public static func += (lhs: inout NSMutableDictionary, rhs: NSDictionary) {
		for (key, valueR) in rhs {
			if let valueL = lhs[key] {
				switch (valueL, valueR) {
				case (let dictionaryL as NSDictionary, let dictionaryR as NSDictionary):
					lhs[key] = dictionaryL + dictionaryR
				case (let arrayL as NSArray, let arrayR as NSArray):
					lhs[key] = arrayL + arrayR
				default:
					lhs[key] = valueR
				}
			}
			else {
				lhs[key] = valueR
			}
		}
	}

}

extension NSArray {

	public static func + (lhs: NSArray, rhs: NSArray) -> NSArray {
		let array = NSMutableArray()
		lhs.forEach { array.add($0) }
		rhs.forEach { array.add($0) }
		return array
	}

}

extension NSMutableArray {

	public static func += (lhs: inout NSMutableArray, rhs: NSArray) {
		for value in rhs {
			lhs.add(value)
		}
	}

}
```

ついでなので `+=` オペレータも追加してみました。

```.swift
var d51 = NSMutableDictionary(dictionary: ["a": 1, "b": 2, "c": 3])
let d52 = NSDictionary(dictionary: ["c": 13, "d": 14, "e": 15])
d51 += d52 // ["a": 1, "b": 2, "c": 13, "d": 14, "e": 15]
```

```.swift
var a21 = NSMutableArray(array: [1, 2, 3])
let a22 = NSArray(array: [4, 5, 6])
a21 += a22 // [1, 2, 3, 4, 5, 6]
```

これで、データの入力が捗る事を期待します。なおコードは以下のリンクからも取得できます。

https://gist.github.com/codelynx/7a59fc1f5cbc14a73036431960e5291b



執筆時の環境に関する情報は以下の通りです。

```
Xcode Version 9.2 (9C40b)
Apple Swift version 4.0.3 (swiftlang-900.0.74.1 clang-900.0.39.2)
Target: x86_64-apple-macosx10.9
```