2015-02-23 1 views
2

Я пишу контейнерный класс в Swift, который работает как java.util.WeakHashMap в Java. Моя текущая реализация здесь.Можно ли подключить, когда освобожден объект с низкой ссылкой (произвольного типа)?

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

В этой реализации holded объектов в контейнере слабо ссылки через WeakBox, поэтому проведение значения не предотвращает объекты от освобождения, когда больше не нужно.

Но ясно, что в этом коде есть проблема; Записи остаются даже после того, как объект его записи освобожден.

Чтобы решить эту проблему, мне нужно зацепить ее перед тем, как освобожденный объект будет выпущен, и удалите его (соответствующую) запись. Я знаю решение только для NSObject, но оно неприменимо к AnyObject.

Может ли кто-нибудь мне помочь? Благодарю. (^_^)

+1

Попробуйте использовать метод 'deinit' - см. [Документы Apple здесь] (https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Deinitialization.html). – pbasdf

+0

Смотрите это: http://stackoverflow.com/questions/25497928/dealloc-in-swift – Ian

+1

Спасибо. Но это не то, что я имел в виду. Класс контейнера, который я создал, является общим классом, поэтому для всех подтипов «AnyObject» он должен работать как «Значение». Подход 'deinit' применим только тогда, когда тип разрешима. – findall

ответ

1

Грустно сказать, didSet или willSet Наблюдатель не вызывается, когда 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.

+0

Это работает для произвольных классов Swift или только для подклассов NSObject (для которых OP утверждает, что уже имеет решение)? –

+0

А, похоже, он не работает для подклассов, отличных от 'NSObject'. :( – rintaro

+0

@MartinR Кажется, он работает в Xcode 6.3 Beta, но не в Xcode 6.1.1. Я не знаю почему. – rintaro

1

Предыдущий пример имеет некоторые ошибки, например:

Неверный размер словаря ошибка: этот пример печатает "1" вместо "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) 
     } 
    } 
}