Search code examples
iosswiftmemory-managementmemory-leaksretain-cycle

Retain cycle in Swift when Object A has Object B as a property and Object B has an array property that contains Object A?


Confused about a situation that might be a retain cycle?

I understand that if

class Object-A {
  var b: Object-B }

and

class Object-B {
    var a: Object-A
]

then above is horrible design because if you do

var a = Object-A()
var b = Object-B()
a.b = b
b.a = a

then this causes a retain cycle with two strong references pointing at each other.

But what if the situation is below?

class Object-A {
    var b: Object-B 
}

class Object-B {
    var randomArrayProperty: [Object-B]
}

and you try to do

var a = Object-A()
var b = Object-B()
a.b = a
b.randomArrayProperty.append(a)

Is that a retain cycle as well?


Solution

  • Generally speaking, with manual reference counting and automatic reference counting, reference ilands will leak. Reference cycle can be composed by more than just 2 objects.

    That said I encourage you to test some code in a playground since the example in your question has some syntax and language errors.

    The code below might be a starting point:

    class ObjectA {
        let name: String
        var b: ObjectB
    
        init(name: String, b: ObjectB) {
            self.name = name
            self.b = b
        }
    
        deinit {
            print("\(name) deallocated")
        }
    }
    
    class ObjectB {
        let name: String
        var randomArrayProperty = [ObjectA]()
    
        init(name: String) {
            self.name = name
        }
    
        deinit {
            print("\(name) deallocated")
        }
    }
    
    // Create a _possible_ leak. It depends on what we'll do next
    var b: ObjectB? = ObjectB(name: "Foo B")
    var a: ObjectA? = ObjectA(name: "Bar A", b: b!)
    b?.randomArrayProperty.append(a!)
    
    // No leaks
    a = nil
    // Remove a from array inside b, a will dealloc
    b?.randomArrayProperty.removeLast()
    b = nil
    
    // Recreate a _possible_ leak.
    b = ObjectB(name: "Foo1 B")
    a = ObjectA(name: "Bar1 A", b: b!)
    b?.randomArrayProperty.append(a!)
    
    // Leaks
    a = nil
    b = nil
    

    Then continue playing with variants ;-)

    Hope this helps