Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Retain cycle when grabing values or keys from Dictionary in Swift

When I grab values from a Dictionary and put them into Array, I can't release memory any more. I tried to remove all object from Array and Dictionary, but these object still exist somewhere (deinit were not called).

I was playing in the following way:

class MyData {
    let i = 0
    init () {
        NSLog("Init")
    }
    deinit {
        NSLog("Deinit")
    }
}

var myDictionary:Dictionary<String, MyData> = ["key1":MyData(), "key2":MyData()] 
// Init was called twice

// here is the problem: extract values from Dictionary
var myValues = Array(myDictionary.values)
myValues = []  // nothing - ok, strong references are still in the dictionary

myDictionary = [:] 
// Why Deinit was not called???

If I remove these two lines for value extraction, then Deinit is called normally

var anotherDictionary:Dictionary<String, MyData> = ["key1":MyData(), "key2":MyData()]
anotherDictionary = [:] 
// Deinit is called twice

var myArray:MyData[] = [MyData(), MyData()]
myArray = []  
// Deinit is called twice

What am I doing wrong here?

How the objects should be removed in the proper way to release memory when they don't needed anymore? The problem happens only when keys or values are extracted from Dictionary (Dictionary.values or Dictionary.keys).

EDIT:

I made a workaround for this case. If I use NSDictionary instead of Dictionary and extract keys first and then take values in a for-loop, then it works.

var myDictionary:NSDictionary = ["key1":MyData(), "key2":MyData()] 
// called Init twice

var myKeys:String[] = myDictionary.allKeys as String[]
var myValues:MyData[] = []

for key in myKeys {
    myValues.append(myDictionary[key] as MyData)
}

myKeys = []
myDictionary = [:]
myValues = []
// Deinit is now called twice, but I'm not sure about keys...

But, if I use allValues instead of allKeys, then it won't work any more.

...
var anotherValues = myDictionary.allValues
anotherValues = []
myDictionary = [:]
// deinit is not called again - very scary...
like image 890
Matjaz Avatar asked Jul 11 '14 14:07

Matjaz


People also ask

What is a retain cycle Swift?

Retain cycles: This is the state when two objects hold weak references to one another. Since the first object's reference count cannot be 0 until the second object is released, and the second object's reference count cannot be 0 until the first objet is released neither object can be released!

What is retaining cycle?

Retain Cycle is the condition When 2 objects keep a reference to each other and are retained, it creates a retain cycle since both objects try to retain each other, making it impossible to release. Here The "Grandparent" retains the "parent" and "parent" retains the "child" where as "child" retains the "parent"..

Is it possible to use a custom struct as a dictionary key Swift?

Any type that conforms to the Hashable protocol can be used as a dictionary's Key type, including all of Swift's basic types. You can use your own custom types as dictionary keys by making them conform to the Hashable protocol.

Which syntax is correct for declare dictionary in Swift?

Which syntax is correct for declare dictionary in Swift? To declare a dictionary you can use the square brackets syntax( [KeyType:ValueType] ). You can initialize a dictionary with a dictionary literal. A dictionary literal is a list of key-value pairs, separated by commas, surrounded by a pair of square brackets.


1 Answers

I don't think this is a retain cycle. I can reproduce this simply by iterating the values in the Dictionary without even introducing the Array, and without doing anything to them. I was playing with this issue when I commented out some of the code and found that this still doesn't call deinit:

    var myDictionary:Dictionary<String, MyData> = ["key1":MyData(), "key2":MyData()]
    for (_, item) in myDictionary {
        //myValues.append(item)
    }
    myDictionary = [:]
    // Why Deinit was not called???

If you take out the for loop entirely, deinit is called as expected. If you put it back in—just looping, not doing anything—then you don't get a deinit.

I'd say this is a bug in dictionary access of some kind; it's not as complicated as reference cycles between Array and Dictionary.

I can reproduce the above with this as my for loop:

for _ in myDictionary { }

...and if I take that line out, it deinits fine. That's the simplest/oddest case I could find.

So, the Array in your example is a red herring, and I think you've found a bug with dictionary access.

like image 179
Matt Gibson Avatar answered Jan 02 '23 06:01

Matt Gibson