Can I hook when a weakly-referenced object (of arbitrary type) is freed?

后端 未结 2 1798
暖寄归人
暖寄归人 2021-01-07 01:15

I\'m writing a container class in Swift, which works like as java.util.WeakHashMap in Java. My current implementation is here.

class WeakRefMap&         


        
相关标签:
2条回答
  • 2021-01-07 01:18

    Previous example has some bugs, for example:

    Invalid size of dictionary bug: this example prints "1" instead of "2":

    let dict = WeakRefMap<String, NSObject>()
    autoreleasepool {
        let val = NSObject()
        dict["1"] = val
        dict["2"] = val
        print("dict size: \(dict.count)")
    }
    

    Fixed WeakRefMap:

    private class DeallocWatcher<Key: Hashable> {
    
        let notify:(keys: Set<Key>)->Void
    
        private var keys = Set<Key>()
    
        func insertKey(key: Key) {
            keys.insert(key)
        }
    
        init(_ notify:(keys: Set<Key>)->Void) { self.notify = notify }
        deinit { notify(keys: keys) }
    }
    
    public class WeakRefMap<Key: Hashable, Value: AnyObject> {
    
        private var mapping = [Key: WeakBox<Value>]()
    
        public init() {}
    
        public subscript(key: Key) -> Value? {
            get { return mapping[key]?.raw }
            set {
                if let o = newValue {
                    // Add helper to associated objects.
                    // When `o` is deallocated, `watcher` is also deallocated.
                    // So, `watcher.deinit()` will get called.
    
                    if let watcher = objc_getAssociatedObject(o, unsafeAddressOf(self)) as? DeallocWatcher<Key> {
    
                        watcher.insertKey(key)
                    } else {
    
                        let watcher = DeallocWatcher { [unowned self] (keys: Set<Key>) -> Void in
                            for key in keys {
                                self.mapping[key] = nil
                            }
                        }
    
                        watcher.insertKey(key)
    
                        objc_setAssociatedObject(o, unsafeAddressOf(self), watcher, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
                    }
    
                    mapping[key] = WeakBox(o)
                } else {
                    if let index = mapping.indexForKey(key) {
    
                        let (_, value) = mapping[index]
                        objc_setAssociatedObject(value.raw, unsafeAddressOf(self), nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
                        mapping.removeAtIndex(index)
                    }
                }
            }
        }
    
        public var count: Int { return mapping.count }
    
        deinit {
            // cleanup
            for e in self.mapping.values {
                objc_setAssociatedObject(e.raw, unsafeAddressOf(self), nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            }
        }
    }
    
    0 讨论(0)
  • 2021-01-07 01:43

    Sad to say, didSet or willSet observer doesn't get called when weak var raw property value is deallocated.

    So, you have to use objc_setAssociatedObject in this case:

    // helper class to notify deallocation
    class DeallocWatcher {
        let notify:()->Void
        init(_ notify:()->Void) { self.notify = notify }
        deinit { notify() }
    }
    
    class WeakRefMap<Key: Hashable, Value: AnyObject> {
    
        private var mapping = [Key: WeakBox<Value>]()
    
        subscript(key: Key) -> Value? {
            get { return mapping[key]?.raw }
            set {
                if let o = newValue {
                    // Add helper to associated objects.
                    // When `o` is deallocated, `watcher` is also deallocated.
                    // So, `watcher.deinit()` will get called.
                    let watcher = DeallocWatcher { [unowned self] in self.mapping[key] = nil }
                    objc_setAssociatedObject(o, unsafeAddressOf(self), watcher, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN_NONATOMIC))
                    mapping[key] = WeakBox(o)
                }
                else {
                    mapping[key] = nil
                }
            }
        }
    
        var count: Int { return mapping.count }
    
        deinit {
            // cleanup
            for e in self.mapping.values {
                objc_setAssociatedObject(e.raw, unsafeAddressOf(self), nil, 0)
            }
        }
    }
    

    NOTE: Before Swift 1.2. this solution does not work for arbitrary Swift classes.

    0 讨论(0)
提交回复
热议问题