How NSMapTable works

4.5k Views Asked by At

I'm trying to figure out how NSMapTable works So I'm trying in playground the following code:

class Person {
    var name: String


    init(name: String ) {
        self.name = name
        print("\(name) is being initialized")
    }

    deinit {
        print("\(name) is being deinitialized")
    }
}

var hobyePerson : NSMapTable? = NSMapTable<Person, NSMutableString>
(keyOptions: .weakMemory, valueOptions: .weakMemory)

var rob : Person? = Person(name: "Rob Appleseed") // print : Rob Appleseed is being initialized

hobyePerson?.setObject("golf", forKey: rob)
hobyePerson?.count // return : 1


rob = nil // print : Rob Appleseed is being deinitialized
hobyePerson?.count // return : 1 (WHY ???!!!!)

as written in the documentation: "Keys and/or values are optionally held “weakly” such that entries are removed when one of the objects is reclaimed."

why even though I initialized the object so that it has a weak reference to the key-value pair when rob is deallocated, I still have an element in hobyePerson?

3

There are 3 best solutions below

1
Luke On

Yes, this is a strange and unfortunate behavior. This article goes into it in some depth. Although it doesn't explore weak-to-weak specifically, the behavior described is the same. As that author notes, hobyePerson.keyEnumerator().allObjects.count and hobyePerson.objectEnumerator().allObjects.count will contain 0 as expected at the end of all this. He also points out that Apple has sort of documented this behavior in the Mountain Lion release notes.

However, weak-to-strong NSMapTables are not currently recommended, as the strong values for weak keys which get zero’d out do not get cleared away (and released) until/unless the map table resizes itself.

Sorry I don't have a better explanation for you.

1
JRG-Developer On

NSMapTable's weak behavior options work best when you don't care when keys/values are released, but rather, you do care that the keys/values aren't strongly retained and will be released at some point after the object of interest becomes nil.

Why so?

As a Foundation class, the authors of NSMapTable had to balance both features and performance.

Consequently, as an "optimization" for performance, they chose that weakly referenced objects that become nil are NOT immediately removed from the map table...! Rather, this happens "later" when it can be efficiently done -- such as when the map table internally gets resized, etc.

As @Luke also mentions in his answer, see this excellent writeup about an experiment done on NSMapTable's behavior for more details:

http://cocoamine.net/blog/2013/12/13/nsmaptable-and-zeroing-weak-references/

0
Renetik On

It didn't work for me so I implemented simple weak map like this.. Will improve it overtime but for now works:

import Foundation

private struct WeakValue<Value:AnyObject> {
    weak var value: Value?
}

public class CSWeakValueDictionary<Key:AnyObject, Value:AnyObject> {
    private let dictionary = NSMutableDictionary()
    public subscript(source: Key) -> Value? {
        get {
            let value = (dictionary["\(source)"] as? WeakValue<Value>)?.value
            if value == nil { dictionary.removeObject(forKey: "\(source)") }
            return value
        }
        set { dictionary["\(source)"] = WeakValue(value: newValue) }
    }
}