LoginSignup
11
10

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-03-26

はじめに

前回、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)
11
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
10