はじめに
前回、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 の String
は AnyObject
ではないので、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)
普通に実行すると以下の通りです。途中で alice
を nil
にしていますが、その直後も alice
は NSMapTable
に残ってしまいます。
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
という縛りがあるので、Int
や struct
は受け付けてくれませんので、ご注意ください。
それではみなさん、Have a happy coding!
EDIT: Swift のバージョンは以下の通りです。
wift --version
Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29)