Should I clean my circular reference?

Former Member
Former Member
Is this really a valid circular reference? I thought not, because Child.mCallback cannot exist without Parent, but simulator says it is. It seems you cannot invoke a weak reference, so using callback.weak() & mCallback.get().invoke("data") will not work. I can clean it up with the lines in MyApp.onStop(state). What I what to know is should I bother with said cleanup?

var globalParent;

class MyApp extends App.AppBase {
function initialize() {
globalParent = new Parent();
globalParent.child.doTask();
}

function onStop(state) {
// Should I bother with this code?
globalParent.child = null;
}
}

class Parent {
var child = new Child(method(:onChild));

function onChild(data) {
// do something with stuff
}
}

class Child {
hidden var mCallback;

function initialize(callback) {
mCallback = callback;
}

function doTask() {
mCallback.invoke("stuff");
}
}
  • Hi sharkbait_au,

    I had a similar problem, and I did add some clean-up code. I used a similar approach to the old Windows DCOM architecture and added a 'finalize' method. This allowed me to remove the circular reference.

    Cheers
    Chris
  • Yes, it is a circular reference. Parent has a reference to Child, Child has a reference to a Lang.Method, and the Lang.Method has a reference back Parent. You cannot solve this by switching to a weak reference to the Lang.Method as the method holds the strong reference back to the parent. You need that strong reference to the parent to be a weak reference.

    You could fix it by not taking a reference to parent until necessary (by passing a reference to the parent to the doTask call on the child), or by keeping a weak reference to the parent from the child (or from child to parent). It seems the latter solution is closer to what you'd want.

    var globalParent;

    class MyApp extends App.AppBase {
    function initialize() {
    globalParent = new Parent();
    globalParent.child.doTask();
    }
    }

    class Parent {
    var child = new Child(self, :onChild);

    function onChild(data) {
    // do something with stuff
    }
    }

    class Child {
    hidden var mParent; // a weak reference to the object to invoke `mSymbol' on.
    hidden var mSymbol; // a symbol for a function we'd like to invoke

    function initialize(parent, symbol) {
    mParent = parent.weak();
    mSymbol = symbol;
    }

    function doTask() {
    var method = mParent.get().method(mSymbol);
    method.invoke("stuff");
    }
    }


    I believe this should eliminate the cycle, and allow Child to still hold a reference to Parent for invoking functions on it.

    Travis
  • Former Member
    Former Member over 8 years ago
    Thank you Travis.

    In and of itself there is no problem with having a circular reference though isn't that right? It is only if it is not dealt with properly that it can become a problem?

    The only time my callback becomes dereferenced is during forced/unplanned app shutdown which allows the app to exit unnaturally while doTask() is running, therefor should I care? I expect the memory to be released when the app exits. As it stands I have currently gone with a call to
    onStop(state) {
    parent.release();
    }

    which is the method that normally cleans up after onChild() has finished during normal program sequence. To clarify, this is code that I'm adding to clean up only in the event that the app has somehow been forced to shut prematurely. In particular by using "Kill App" in the simulator, which allows the app to exit unnaturally while doTask() is running.
  • Yes.

    Having a circular reference is only really a problem if it is causing your application to leak memory. If you create one at program startup and leave it, there is no issue. The memory should be reclaimed by the system when your application exits.

    If you are creating these objects often, you would obviously need to take care to avoid the cycles or to break them. I personally feel it is simpler to avoid the cycles in the first place. If you do it that way the simulator won't nag you every time the app exits (regardless of how cleanly the exit happens).

    Travis
  • Former Member
    Former Member over 8 years ago
    .....the simulator won't nag you every time the app exits (regardless of how cleanly the exit happens).
    This line makes me question if you realize that my app does not return a circular reference error when I run it? That I have to kill the app in the simulator at a specific point of execution to create the error. Something that a user cannot do from within the app when it is running.

    As for the method of creating a non-referenced callback I think it is good. However there is a part of me that questions why we should need to, considering that inbuilt functions can be passed a method and do not produce the warning. Is it that the inbuilt functions have a destructor that we don't have access to?
  • No. I understand you've worked around this particular case so that it exits cleanly if the app calls onStop(). I've run into cases where it is difficult to break the cycles, mostly because there aren't good places to insert cleanup code. IMO, it is often easier to avoid the cycle in the first place.

    Some built-in functions do take Lang.Method objects (which implicitly hold strong references to the object) as parameters (the Sensor.enableSensorEvents() and Position.enableLocationEvents() functions are common examples). There is no cycle in those cases though. The framework has a reference to your object (through the callback), but your object has no reference to anything that directly or indirectly references your object. As long as we don't have access to the thing that holds the Lang.Method callbacks, there is no cycle, so there is no problem.
  • Former Member
    Former Member over 8 years ago
    You can make use of circular references if that is your preference. The error that is produced by the VM is there to let you know that you have the potential for a memory leak. Circular references that are created when the app starts and destroyed when the app closes, won't effect your performance.

    The thing you need to watch out for (and the reason people suggest avoiding these entirely), is that as you build more complex code, you might reuse an object in a new context that is created and destroyed continually during your app's lifecycle. If you don't clean up properly in this situation, you can end up leaking memory, and will eventually crash from an "out of memory" error.
  • Former Member
    Former Member over 8 years ago
    Thanks Brandon. And that is my point with this example. Since Parent creates Child there is no way Child can live without Parent, so therefor no way a circular reference can exist within the program. Isn't that right?
  • Former Member
    Former Member over 8 years ago
    I'm not sure I follow. You are creating a parent object within your AppBase that creates a child object that holds a reference to itself. This creates a circular reference. You remove that reference by breaking the chain in onStop(). This is all generally fine. You actually won't even hurt anything if you don't clean up this reference, because the VM will destroy the leaked objects when your app closes right after onStop(), but this cleanup code is where the error log comes from (so if you want to eliminate the error, you need to remove it).

    The concern from a design standpoint comes from the standpoint of code reuse. Using this parent class is dependent on having a place to run the destruction. In your current design, onStop() is an appropriate place for it to happen, but if you, for example, wanted to use this class from a View, instead of from AppBase, there may not be an appropriate place for the destruction to run.

    If objects had destructors, it would be a perfect place remove the circular reference, but it isn't something that is currently available.
  • Former Member
    Former Member over 8 years ago
    What I'm trying to say, is that as I understand it, in my example the only way for a circular reference to exist when it shouldn't is if Parent gets deleted and Child is left with it. But Child can't exist without Parent, so it's impossible for it to exist isn't it?