NSMapTable の挙動について調べてみた

`NSMapTable` の `Key` の挙動に不信感を抱いたので少し調べてみました。ObjC時代にも調べたような気がしますが、記憶が定かではないので、自分へのメモ。

---

`key` が `NSString` の場合。`"string" as NSString` とか書いている時点で、異なるオブジェクトをキーにしているような気もするのですが、なぜか一致します。まぁ、`NSString`の場合はそうでなければ不便なのでいいとします。

```.swift
var key: NSString
key = "east"
mapTable.setObject(1024 as NSNumber, forKey: key as NSString)
key = "west"
mapTable.setObject(512 as NSNumber, forKey: key as NSString)

mapTable.object(forKey: "east" as NSString) // "1024" as expected
mapTable.object(forKey: "west" as NSString) // "512" as expected
```

`key` が `NSMutableString` の場合で `setString()` でその値を変えてみました。なんか不思議な挙動ですね。table内のキーは取り出せなくなってしまったのでしょうか。

```.swift
let mapTable = NSMapTable<NSString, NSNumber>()
var key: NSMutableString
key = NSMutableString(string: "north")
mapTable.setObject(256 as NSNumber, forKey: key)
key.setString("south")

mapTable.object(forKey: "north" as NSString) // nil
mapTable.object(forKey: "south" as NSString) // nil
mapTable.object(forKey: key) // nil -- why??
mapTable.keyEnumerator().allObjects // ["south"] wow!!
```

`key` が同じ値でも、別オブジェクトでアクセス。同一キーとして扱われています。

```.swift
let mapTable = NSMapTable<NSString, NSNumber>()
var key1 = NSMutableString(string: "north")
var key2 = NSMutableString(string: "north")
mapTable.setObject(64 as NSNumber, forKey: key1)
mapTable.setObject(2048 as NSNumber, forKey: key2)

key1.setString("north")
mapTable.object(forKey: key1) // "2048" as expected
mapTable.keyEnumerator().allObjects // ["north"]
```

`key` が `NSDictionary` の場合。 `key`と`value`の定義する順番が異なっても、同じオブジェクトとして扱ってくれるようです。また、`NSMutableDictionary` に `setDictionary()` で同じ`Key`と`Value`のセットを与えても、同じキーと判断してくれるようです。

```.swift
let mapTable = NSMapTable<NSDictionary, NSString>()
let key1a = NSDictionary(dictionary: ["city": "Tokyo", "color": "blue"])
let key1b = NSDictionary(dictionary: ["color": "blue", "city": "Tokyo"])
let key1ax = NSMutableDictionary(dictionary: ["city": "Tokyo", "color": "blue"])
var key1c = NSMutableDictionary()

key1c.setDictionary(["city": "Tokyo", "color": "blue"])
mapTable.setObject("Japan" as NSString, forKey: key1a)
mapTable.object(forKey: key1b) // "Japan"
mapTable.object(forKey: key1ax) // "Japan"
mapTable.object(forKey: key1c) // "Japan"
```

`key` を `NSMutableDictionary` にして 値をセット、その後 `key` の値を変更。すると、テーブル内のキーも変更され、キーはオブジェクトのように扱われているようです。

```.swift
let mapTable = NSMapTable<NSDictionary, NSString>()
var key1 = NSMutableDictionary(dictionary: ["city": "Tokyo", "color": "blue"])
mapTable.setObject("Apple" as NSString, forKey: key1)
key1.setDictionary(["color": "red", "city": "Tokyo"]) // "red" !?
mapTable.object(forKey: key1) // "Apple"
mapTable.object(forKey: ["city": "Tokyo", "color": "blue"]) // nil
mapTable.keyEnumerator().allObjects // [["color": "red", "city": "Tokyo"]]
```

自分のクラスの場合です。`Hashable`（`Equatable`）を実装しても、実質的等価なオブジェクトも同じキーとは扱えないようで、これはこれで期待通りの挙動だったりします。

```.swift
class MyObject: Hashable {
	var name: String
	init(name: String) {
		self.name = name
	}
	var hashValue: Int { return name.hashValue }
}

func == (lhs: MyObject, rhs: MyObject) -> Bool {
	return lhs.name == rhs.name
}

let mapTable = NSMapTable<MyObject, NSString>()
let obj1a = MyObject(name: "red")
let obj1b = MyObject(name: "red")
let obj2 = MyObject(name: "blue")
obj1a == obj1b // true
mapTable.setObject("Orange", forKey: obj1a)
mapTable.object(forKey: obj1a) // "Orange"
mapTable.object(forKey: obj1b) // nil
mapTable.object(forKey: obj2) // nil
```