Swiftで弱参照コンテナ

今日のテーマ

  • 問題点: Swiftのコンテナをそのまま使うとオブジェクトを強参照してしまう
  • やること: オブジェクトの弱参照を格納するコンテナを作る

Swift復習:うんこの生成と消滅

まずは普通にクラスを作って、メモリの確保と解放が行われることを確認するよ。

// 一意なIDを返すIDシーケンサ
public struct IDSequensor {
    private static var _id:UInt64 = 0
    public static var ID:UInt64 {
        get { return _id++ }
    }
}

// てきとうなクラス
public class Unko {
    private let myId:UInt64
    public init(id: UInt64) {
        myId = id
        println("ID:\(myId)のインスタンスが作られました")
    }
    
    deinit {
        println("ID:\(myId)のインスタンスが殺されました")
    }
}

// ウンコインスタンスを作って…
var unko:Unko? = Unko(id: IDSequensor.ID)

// 殺す
unko = nil

これを実行すると、コンソールに以下のメッセージが出力されます。ここまでは想定の範囲内

ID:0のインスタンスが作られました
ID:0のインスタンスが殺されました
Program ended with exit code: 0

失敗例:コンテナを普通に使う

少しステップアップして、生成したうんこオブジェクトをコンテナに貯蔵する仕組みを考えてみるよ。

  • うんこが作られると同時に、コンテナに登録
  • うんこが死ぬと同時に、コンテナから削除

オブジェクトの生死と連動しているので、まずは素直にイニシャライザとデイニシャライザの中に登録と削除の処理を書いてみます。

初めに言っておきますが、この方法は失敗します

// てきとうなクラス
public class Unko {
    private let myId:UInt64
    public init(id: UInt64) {
        myId = id
        
        // New!!
        // 生まれた瞬間うんこプールに追加(なんだこのネーミング…)
        globalUnkoPool[myId] = self
        
        println("ID:\(myId)のインスタンスが作られました")
    }
    
    deinit {
        
        // New!!
        // 死に際にうんこプールから削除
        globalUnkoPool.removeValueForKey(myId)
        
        println("ID:\(myId)のインスタンスが殺されました")
    }
}

// New!!
// グローバルうんこプール
// 作ったウンコが自動的に溜められるコンテナ
var globalUnkoPool : [UInt64 : Unko] = [:]



// ウンコインスタンスを作って…
var unko:Unko? = Unko(id: IDSequensor.ID)

// 殺す
unko = nil

これを実行すると、なんとデイニシャライザが動きません。メモリリークです。

ID:0のインスタンスが作られました
Program ended with exit code: 0

上記コードでは、グローバルうんこプールがうんこインスタンスを強参照しているせいで、うんこオブジェクトを生成すると参照カウンタがいきなり2になってしまいます。上記のコードでうんこに nil を代入しても、参照カウンタが 1 しか減らないため、いつまでもメモリが解放されない問題を孕んでいます。

最終成果:弱参照コンテナ

以下の例では、オブジェクトの弱参照をコンテナに入れるようにしています。

// 一意なIDを返すIDシーケンサ
public struct IDSequensor {
    private static var _id:UInt64 = 0
    public static var ID:UInt64 {
        get { return _id++ }
    }
}

// 任意の型の弱参照を保持するクラス
public class Weak<T: AnyObject> {
    private weak var _element:T?
    public init(element: T) {
        _element = element
    }
}

// てきとうなクラス
public class Unko {
    private let myId:UInt64
    public init(id: UInt64) {
        myId = id
        
        // 生まれた瞬間のうんこをWeakで包んでプールに追加
        globalUnkoPool[myId] = Weak(element: self)
        
        println("ID:\(myId)のインスタンスが作られました")
    }
    
    deinit {
        
        // 死に際にうんこプールから削除
        globalUnkoPool.removeValueForKey(myId)
        
        println("ID:\(myId)のインスタンスが殺されました")
    }
}

// グローバルうんこプール
// 作ったウンコの弱参照が自動的に溜められるコンテナ
var globalUnkoPool : [UInt64 : Weak<Unko>] = [:]



// ウンコインスタンスを作って…
var unko:Unko? = Unko(id: IDSequensor.ID)

// 殺す
unko = nil

実行すると、ちゃんとうんこが死にます。やったね。

ID:0のインスタンスが作られました
ID:0のインスタンスが殺されました
Program ended with exit code: 0

参考: objective c - How do I declare an array of weak references in Swift? - Stack Overflow

Copyright (c) 2012 @tercel_s, @iTercel, @pi_cro_s.