Swift で Key や Value に弱参照が使える Dictionary 的な方法を考える

# はじめに

前回、[Weak Object を Collection として扱う方法](http://qiita.com/codelynx/items/b5bb9199d01d63d590e0)を記事にしました。これは、弱参照で複数のオブジェクトを保持するクラスを紹介しました。すると、次は、`Dictionary` 形式で、Key や Value に弱参照を使いたくなるのは自然な流れです。今回は方法を紹介したいと思います。

そんなコードを一生懸命考えていたのは、[iOS馬場](http://iosbaba.connpass.com)の勉強会の会場内です。

#設計方針

Foundation に NSMapTable があります。これは、Key または Value の片方または両方に弱参照、強参照のオブジェクトが扱えますが、型の意識は全くありません。そこで、Generic を使って、Key と Value を型で縛った、クラスを設計してみたいと思います。

まずは、素直に Swift の Generics で wrap しただけのクラスを書いてみました。とりあえず、「強参照の Key と弱参照の Value を紐付ける」`ZStrongToWeakMapTable`、と「弱参照の Key と強参照の Value を紐付ける」`ZWeakToStrongMapTable` を普通に書いてみたいと思います。

```.swift
class ZStrongToWeakMapTable<K: AnyObject, V: AnyObject>: CustomStringConvertible {

	let mapTable: NSMapTable

	init() {
		self.mapTable = NSMapTable.strongToWeakObjectsMapTable()
	}

	subscript(key: K) -> V? {
		get {
			return self.mapTable.objectForKey(key) as? V
		}
		set {
			self.mapTable.setObject(newValue, forKey: key)
		}
	}

	var description: String {
		return mapTable.description
	}
}

class ZWeakToStrongMapTable<K: AnyObject, V: AnyObject>: CustomStringConvertible {

	let mapTable: NSMapTable

	init() {
		self.mapTable = NSMapTable.weakToStrongObjectsMapTable()
	}

	subscript(key: K) -> V? {
		get {
			return self.mapTable.objectForKey(key) as? V
		}
		set {
			self.mapTable.setObject(newValue, forKey: key)
		}
	}

	var description: String {
		return mapTable.description
	}
}
```

Genericsで Key を `K`、Value を `V` と型に縛りを入れているので、クライアントコードは Swift らしく型を意識したプログラミングが可能になります。


ではテストしてみましょう。弱参照の Key から強参照の Value を紐付けるクラス `ZWeakToStrongMapTable` を使ってみます。`Dictionary` と同様に `[key]` でアクセスできます。今回は Key も Value も文字列にしてみますが、Swift の `String` は `AnyObject` ではないので、`NSString` を使います。

```.swift

let mapTable = ZWeakToStrongMapTable<NSString, NSString>()

var alice: NSString? = "Alice"
var bob: NSString? = "Bob"
var cathline: NSString? = "Cathline"

var toronto: NSString? = "Toronto"
var newyork: NSString? = "New York"
var tokyo: NSString? = "Tokyo"

mapTable[alice!] = toronto
mapTable[bob!] = newyork
mapTable[cathline!] = tokyo
print(mapTable.description)

alice = nil
print(mapTable.description)
```

普通に実行すると以下の通りです。途中で `alice` を `nil` にしていますが、その直後も `alice` は `NSMapTable` に残ってしまいます。

```.console
NSMapTable {
[3] Bob -> New York
[8] Alice -> Toronto
[15] Cathline -> Tokyo
}

NSMapTable {
[3] Bob -> New York
[8] Alice -> Toronto
[15] Cathline -> Tokyo
}
```

ところが、以下のようにタイミングをちょっとだけ遅らせて参照すると、alice が消えている事が確認できます。どんなタイミングで `NSMapTable` から消えるかは確認できていません。<font color="silver">Run Loopの終わりじゃないかと言う噂も…</font>

```.swift
dispatch_async(dispatch_get_main_queue()) {
	print(self.mapTable.description)
}
```

```.console
NSMapTable {
[3] Bob -> New York
[15] Cathline -> Tokyo
}
```

とにかく、動作する事が確認できたのですが、この `NSMapTable` は、Key と Value に 弱参照と強参照の組み合わせで４つのモデルがあります。よって同様の実装を４種類実装する必要があります。具体的には先のコードで <code>NSMapTable.</code><font color='red'>weakToStrongObjectsMapTable</font><code>()</code> の部分にバリエーションがあるだけで後は全く同じです。仕方ないので、同じようなコードを４回書くのは、使用上は全く問題がありませんが、エンジニアとしては残念な感じがします。

	• Weak -> Strong
	• Strong -> Weak
	• Weak -> Strong
	• Strong -> Strong

そこで、どうやって、この重複したコードをすっきりさせるかが課題となります。方法はいくつかあるのですが、本題の趣旨から逸脱するので、別のトピックとしたいと思います。ここではそのやり方の一つを紹介したいと思います。

[Swiftでほんの一部しか違わないクラスのバリエーションの実装方法の考察](http://qiita.com/codelynx/items/08d73ae3e6f1e5c0c97f)

## サブクラス化して、スーパークラスに共通のコードを押し込む 

```.swift
class ZMapTable<K: AnyObject, V: AnyObject>: CustomStringConvertible {
	let mapTable: NSMapTable
	private init(mapTable: NSMapTable) {
		self.mapTable = mapTable
	}
	subscript(key: K) -> V? {
		get { return self.mapTable.objectForKey(key) as? V }
		set { self.mapTable.setObject(newValue, forKey: key) }
	}
	var description: String {
		return mapTable.description
	}
}

class ZStrongToWeakMapTable<K: AnyObject, V: AnyObject>: ZMapTable<K, V> {
	init() {
		super.init(mapTable: NSMapTable.strongToWeakObjectsMapTable())
	}
}

class ZWeakToStrongMapTable<K: AnyObject, V: AnyObject>: ZMapTable<K, V> {
	init() {
		super.init(mapTable: NSMapTable.weakToStrongObjectsMapTable())
	}
}

class ZWeakToWeakMapTable<K: AnyObject, V: AnyObject>: ZMapTable<K, V> {
	init() {
		super.init(mapTable: NSMapTable.weakToWeakObjectsMapTable())
	}
}

class ZStrongToStrongMapTable<K: AnyObject, V: AnyObject>: ZMapTable<K, V> {
	init() {
		super.init(mapTable: NSMapTable.strongToStrongObjectsMapTable())
	}
}
```

繰り返しますが、Key も Value も `AnyObject` という縛りがあるので、`Int` や `struct` は受け付けてくれませんので、ご注意ください。

それではみなさん、Have a happy coding!


EDIT: Swift のバージョンは以下の通りです。

```.console
wift --version
Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29)
```