【问题标题】:Can I hook when a weakly-referenced object (of arbitrary type) is freed?当弱引用对象(任意类型)被释放时,我可以挂钩吗?
【发布时间】:2015-02-23 09:45:49
【问题描述】:

我正在用 Swift 编写一个容器类,它的工作方式类似于 Java 中的 java.util.WeakHashMap。我当前的实现在这里。

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 {
                mapping[key] = WeakBox(o)
            }
            else {
                mapping.removeValueForKey(key)
            }
        }
    }

    var count: Int { return mapping.count }
}

class WeakBox<E: AnyObject> {
    weak var raw: E!
    init(  _ raw: E) { self.raw = raw }
}

在此实现中,容器中持有的对象通过WeakBox 进行弱引用,因此持有值永远不会阻止对象在不再需要时被释放。

但显然这段代码有问题;即使在其条目的对象被释放后,条目仍然存在。

要解决这个问题,我需要在释放持有的对象之前进行挂钩,并删除其(相应的)条目。我只知道NSObject 的解决方案,但不适用于AnyObject

谁能帮帮我?谢谢。 (^_^)

【问题讨论】:

标签: ios swift dictionary weak-references


【解决方案1】:

很遗憾,didSetwillSet 观察者在 weak var raw 属性值被释放时不会被调用。

所以,在这种情况下你必须使用objc_setAssociatedObject

// 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)
        }
    }
}

注意:在 Swift 1.2 之前。此解决方案不适用于任意 Swift 类。

【讨论】:

  • 这适用于任意 Swift 类还是仅适用于 NSObject 的子类(OP 声称已经有解决方案)?
  • 啊,看来,它不适用于非NSObject 子类。 :(
  • @MartinR 看来,它在 Xcode 6.3 Beta 中有效,但在 Xcode 6.1.1 中无效。我不知道为什么。
  • 非常感谢,@rintaro san!我错误地认为objc_setAssociatedObject 不适用于AnyObject,但现在我测试并注意到它在 Swift 1.2 中有效。 (但不是在旧版本中。)
【解决方案2】:

前面的例子有一些bug,例如:

字典大小无效错误:此示例打印“1”而不是“2”:

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

固定的 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)
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-02-15
    • 1970-01-01
    • 2013-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多