The examples about strong reference cycles I usually see involve two classes with properties pointing to each other. However, what if only one of the classes has a property pointing to the other instance like this:
class ClassA {
var classB: ClassB? = nil
}
class ClassB {
}
Then I create my instances like this:
var myClassA = ClassA()
var myClassB = ClassB() //Reference count 1
myClassA.classB = myClassB //Reference count 2
// Now deallocate
myClassB = nil //Reference count 1
myClassA = nil
Since I've deallocated myClassB
, the reference count is 1. What happened to the reference count of myClassA.classB
? It never reached zero since I never did myClassA.classB = nil
or used deinit
to do this. Is this implicitly done since I did myClassA = nil
?
Is this what can be categorized as a strong reference cycle? I would imagine that it is at least a memory leak though, is this true?
As @ozgur, @jtbandes, @Avi, and @Rob explained in the comments, there is no strong reference cycle or leak.
Here is an example based upon @Rob's comment that you can run in a Playground:
class ClassA {
var classB: ClassB?
deinit {
print("ClassA deallocated")
}
}
class ClassB {
deinit {
print("ClassB deallocated")
}
}
class Tester {
func test() {
var myClassA: ClassA! = ClassA()
var myClassB: ClassB! = ClassB() //Reference count 1
myClassA.classB = myClassB //Reference count 2
// Now deallocate
print("setting myClassB to nil")
myClassB = nil //Reference count 1
print("setting myClassA to nil")
myClassA = nil
print("all done")
}
}
// Create `Tester` object and call `test`:
Tester().test()
Output:
setting myClassB to nil setting myClassA to nil ClassA deallocated ClassB deallocated all done
The thing to note is that even though you set myClassB
to nil
first, that myClassA
gets freed first. When myClassA
gets freed, the final reference to myClassB
is released by ARC and then myClassB
is freed.
To demonstrate a strong reference cycle, have ClassB
retain a strong reference to ClassA
:
class ClassA {
var classB: ClassB?
deinit {
print("ClassA deallocated")
}
}
class ClassB {
var classA: ClassA?
deinit {
print("ClassB deallocated")
}
}
class Tester {
func test() {
var myClassA:ClassA! = ClassA()
var myClassB:ClassB! = ClassB() //Reference count 1
myClassA.classB = myClassB //Reference count 2
myClassB.classA = myClassA
// Now deallocate
print("setting myClassB to nil")
myClassB = nil //Reference count 1
print("setting myClassA to nil")
myClassA = nil
print("all done")
}
}
Tester().test()
Output:
setting myClassB to nil setting myClassA to nil all done
Neither object is deallocated if they both contain strong reference to the other. To break this strong reference cycle, declare one of the classB
or classA
properties to be weak
. Which one you choose affects the order that the objects get freed:
If you declare weak var classB: ClassB
in ClassA
:
Output:
setting myClassB to nil ClassB deallocated setting myClassA to nil ClassA deallocated all done
If instead, you declare weak var classA: ClassA in ClassB
:
Output:
setting myClassB to nil setting myClassA to nil ClassA deallocated ClassB deallocated all done