1

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?

Rob
  • 415,655
  • 72
  • 787
  • 1,044
TruMan1
  • 33,665
  • 59
  • 184
  • 335
  • 2
    There is neither a leakage nor a circular reference in your code. When you set `myClassA` to `nil`, ARC automatically breaks ownership of `ClassB` so both of them get deallocated. – Ozgur Vatansever May 22 '16 at 06:25
  • To expand on that — there is no cycle, since your graph only has one edge: `myClassA.classB -> myClassB`. – jtbandes May 22 '16 at 06:26
  • If that code caused a link, it would make ARC only marginally useful. All references held at the instance level are released when the instance is released. – Avi May 22 '16 at 06:26
  • 2
    All of the above comments are quite right. BTW, if you add `deinit` method to both classes that prints something, you can empirically verify that the instance of `ClassA` and that of `ClassB` are both getting deallocated. Also, probably needless to say, but `myClassA` and `myClassB` both have to be defined as optionals in order to set them to `nil`. – Rob May 22 '16 at 07:08
  • I added an example based upon @Rob's comment to demonstrate in a Playground: 1) the original code doesn't have a strong reference cycle, 2) what it would take to create a strong reference cycle, 3) how to break that strong reference cycle. – vacawama May 22 '16 at 13:07
  • That's a relief, thx everyone! I guess this is because of ARC, otherwise you'd have to set `myClassA.classB` to nil in ClassA's `deinit`. – TruMan1 May 22 '16 at 16:23

1 Answers1

2

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
vacawama
  • 150,663
  • 30
  • 266
  • 294