Swift Dictionary の key, value を weak 参照したい
たとえば
UIView
を key にして、Date
を value にした Dictionary
を使いたいと考えたとき。
ここで問題があるのは Dictionary の key, value は strong
で参照するため、 UIView を key にするとメモリリークが発生する。(本来の UView のライフサイクルが終了しても、解放されない)
この対応策が以下。
2つ方法を書いたが、他にもあると思う。
1. NSMapTable を使用する
NSMapTable
は Dictionary と似ているが、key と value の参照を weak, strong からそれぞれ選ぶことができる。
https://developer.apple.com/documentation/foundation/nsmaptable
key を weak, value を strong にすると以下のコードになる。
let table: NSMapTable = NSMapTable<UIView, NSDate>(keyOptions: .weakMemory, valueOptions: .strongMemory) autoreleasepool { let view1 = UIView() table.setObject(Date() as NSDate, forKey: view1) } let view2 = UIView() table.setObject(Date() as NSDate, forKey: view2) let view3 = UIView() table.setObject(Date() as NSDate, forKey: view3) // 追加した要素は3個だが、うち1つは autoreleasepool ブロック内でライフサイクルが終わっている(=weak 参照の NSMapTable の要素も消える)ので、count は 2 となる。 print(table.count)
2. NSCache を使用する
要素の数が限られていて良い。という場合は、NSCache
を使う方法も取れる。
その場合、UIView をそのままキーにするのではなく ObjectIdentifier
を key にする。
https://developer.apple.com/documentation/swift/objectidentifier
ObjectIdentifier を key にすることで、UIView の参照カウントを増やさずに、UIView を一意なキーとして Dictionary で扱える。
コードはこんな感じ。
let cache = NSCache<AnyObject, NSDate>() cache.countLimit = 2 let view1 = UIView() cache.setObject(Date() as NSDate, forKey: ObjectIdentifier(view1) as AnyObject) let view2 = UIView() cache.setObject(Date() as NSDate, forKey: ObjectIdentifier(view2) as AnyObject) let view3 = UIView() cache.setObject(Date() as NSDate, forKey: ObjectIdentifier(view3) as AnyObject) // キャッシュ最大個数(2)を超えているので、一番最初に追加した要素はなくなっている print("view1 -> \(cache.object(forKey: ObjectIdentifier(view1) as AnyObject))") // view1 -> nil print("view2 -> \(cache.object(forKey: ObjectIdentifier(view2) as AnyObject))") // view2 -> Optional(2022-01-09 06:12:40 +0000) print("view3 -> \(cache.object(forKey: ObjectIdentifier(view3) as AnyObject))") // view3 -> Optional(2022-01-09 06:12:40 +0000)