Type based reference cycle detection


(Darko Damjanovic) #1

The one thing which I would need most when working with Swift would be type based reference cycle detection during compile time. I have discussed a similar topic already in the developer forums a long time ago and also received some response from one of the Apple Developers. (I think it was Douglas Gregor from this list but I can't find the thread anymore) The answer at this time was that type based reference cycle detection is problematic, but I can not remember the reason anymore. I think the reason was: it is too general and would generate many false positives.

Currently I am working for almost one whole year on a Swift project. (iOS) I have written a few thousands lines of code and of course also experienced some memory leaks. And literally all of them happened in their most basic form:

    class A {
      let myB: B?
    }
    
    class B {
       let myA: A?
    }
    
Which is basically used by the delegate pattern. I understand that cycle detection in a complex graph of objects is not reasonable during compile time. And there are also many cases which can only be found during runtime. I understand also the disadvantages of full runtime-garbage collection. But this is not what I need. The only thing which I miss most is a simple warning of the compiler when type A and B have strong references to each other. (if more is possible - even better)

For example this tool is doing it already: http://j2objc.org/docs/Cycle-Finder-Tool.html

If I am still on the wrong track then I would love to know why. Thanks.

- Darko


(Daniel Duan) #2

Darko Damjanovic via swift-evolution <swift-evolution@...> writes:

most basic form:

    class A {
      let myB: B?
    }

    class B {
       let myA: A?
    }

One workaround available today is the replace classes with structs. Due to
struct's value semantics, the complier have to (and does) ban the relationship
in this example. (I understand this is not very practical when it comes to
    UIKit components).

For reference types such as class, a warning about retain cycle seems
reasonable. But there should also be a way to silence the warning. I can see
deliberate use cases for this example, such as abandoning weak references to
avoid performance hit.

Implementing this feature would be…interesting. Example:

     class A { let myP: P? }

     class P { let myX: AnyObject? }

Should this code generate a cycle warning? What if myP's type is
GreatGreatGreatGrandParentOfP? Should any two class with a strong reference to
AnyObject generate a warning? We have to answer these questions before even
talking about how to implement it.


(Darko Damjanovic) #3

    class A { let myP: P? }

    class P { let myX: AnyObject? }

Should this code generate a cycle warning? What if myP's type is
GreatGreatGreatGrandParentOfP? Should any two class with a strong reference to
AnyObject generate a warning?

Of course not. Extending the attempt to solve reference cycles in all special cases like inherited types, protocols, generics (is an Array<P> a P?) etc... would lead immediately to the thought "this is not possible to solve in a clean way“. And I think it would be true.

In my experience in app development almost 100% of all reference cycles where caused by the most simple form. And I don't have hard statistical data about it, just my own experience. But let's assume that 80% of all strong reference cycles during app development are in this simple form. Or just 50%. Wouldn't it still be great to have a warning in 50% of all possible strong reference cycles even if not all other cases are considered?

- Darko


(Daniel Duan) #4

Note I agree that a warning about retain cycle is a good idea. Otherwise
I would not bring up how to implement it at all. (I happened to have
implemented the cycle detection for value types in swiftc. During that process
I thought about this very issue, a lot.)

The key phrase here is “simple form”. We need to translate this into code at
some point, no?

···

On Feb 28, 2016, at 10:26 AM, Darko Damjanovic <darkodamjanovic@me.com> wrote:

   class A { let myP: P? }

   class P { let myX: AnyObject? }

Should this code generate a cycle warning? What if myP's type is
GreatGreatGreatGrandParentOfP? Should any two class with a strong reference to
AnyObject generate a warning?

Of course not. Extending the attempt to solve reference cycles in all special cases like inherited types, protocols, generics (is an Array<P> a P?) etc... would lead immediately to the thought "this is not possible to solve in a clean way“. And I think it would be true.

In my experience in app development almost 100% of all reference cycles where caused by the most simple form. And I don't have hard statistical data about it, just my own experience. But let's assume that 80% of all strong reference cycles during app development are in this simple form. Or just 50%. Wouldn't it still be great to have a warning in 50% of all possible strong reference cycles even if not all other cases are considered?

- Darko


(Darko Damjanovic) #5

Note I agree that a warning about retain cycle is a good idea. Otherwise
I would not bring up how to implement it at all. (I happened to have
implemented the cycle detection for value types in swiftc. During that process
I thought about this very issue, a lot.)

The key phrase here is “simple form”. We need to translate this into code at
some point, no?

Sorry, I did not want to sound offending. It’s just that the discussion in the developer forums ended soon because of too high expectations.

Regarding the implementation: I really have no idea. I am an application developer for 28 years but I do not have any experience in compiler development. From my point of view the compiler just should issue a warning about a reference cycle so I can mark the reference weak or unowned. This would be enough for me.

- Darko


(Darko Damjanovic) #6

The key phrase here is “simple form”. We need to translate this into code at
some point, no?

With „simple form“ I really mean the simplest possible form:

    class A {
         var myB: B?
    }

    class B {
        var myA: A?
    }

So exactly class A and exactly class B. If I understand it correct then the detection of inherited or derived types (or Protocols) would only be possible during runtime.