Not solvable retain-cycles

The following example shows the implementation of a retain cycle, which is not solvable.

// In framework one (not open)
public protocol Animal {
    var name: String { get }
    var farm: AnyObject? { get set }
}

class Cat: Animal {
    let name = "Lulu"
    var farm: AnyObject?

}

// In framework two
class Farm: ObservableObject {
    var animal: Animal
    
    init(animal: Animal) {
        self.animal = animal
        self.animal.farm = self
    }
}

The solution would be that the owner of framework one changes the requirements of the Animal protocol, so that it follows AnyObject.

One solution could be that the compiler checks the implementation of the protocols and throws an error if the protocol implementation is only used in classes.

Have you considered making the reference to farm weak? If protocol Animal: AnyObject {} you'll still run into a retain cycle. But I don't think the compiler needs to do more here. Some issues are simply architectural, not compiler issues.

1 Like

I found a solution, it could be solved like this:

class Farm: ObservableObject {
    weak var animal: (Animal&AnyObject)?
    
    init(animal: Animal&AnyObject) {
        self.animal = animal
        self.animal?.farm = self
    }
}```

Not very nice but at least no reference-cycle.

I think the only "good" solution would be to update the code in framework one, but if that is not an option you could add something like this in framework two to make it look slightly nicer:

// In framework two
protocol AnimalClass: AnyObject, Animal { }
extension Cat: AnimalClass {}

class Farm: ObservableObject {
    weak var animal: AnimalClass?

    init(animal: AnimalClass) {
        self.animal = animal
        self.animal?.farm = self
    }
}

Conceptually, I think Cat would be the place to have a weak farm variable and not vice versa, but I get that is not an option.

As others noted, the fundamental bug is arguably in framework one, which probably should have written

class Cat: Animal {
    let name = "Lulu"
    weak var farm: AnyObject?
}

If you can't fix framework one, you might still be able to manually break the cycle by assigning self.animal.farm = nil yourself when you know the Farm is no longer needed.

3 Likes