Swift
Generics
NSMapTable

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

More than 3 years have passed since last update.


はじめに

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

そんなコードを一生懸命考えていたのは、iOS馬場の勉強会の会場内です。


設計方針

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

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

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 の StringAnyObject ではないので、NSString を使います。


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)

普通に実行すると以下の通りです。途中で alicenil にしていますが、その直後も aliceNSMapTable に残ってしまいます。

NSMapTable {

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

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

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

dispatch_async(dispatch_get_main_queue()) {

print(self.mapTable.description)
}

NSMapTable {

[3] Bob -> New York
[15] Cathline -> Tokyo
}

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

• Weak -> Strong

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

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

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 という縛りがあるので、Intstruct は受け付けてくれませんので、ご注意ください。

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

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

wift --version

Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29)