How to Manage Memory Leaks in Titanium Apps

Memory leaks

JavaScript like many languages can be prone to memory leaks in certain situations. If you’ve developed in JavaScript, you might be familiar with a few causes like accidentally creating global variables, forgetting callbacks and timers, and not cleaning up event handlers.

When it comes to cross-platform native apps written in JavaScript, it’s even more important to be aware of the possibility of memory leaks – especially when platforms like iOS are aggressive with application memory management and will shut down a “misbehaving” app.

In Titanium, it’s possible to create memory leaks when objects and views are not cleaned up properly when a window is closed or view removed, and this can lead to a window staying in memory, appearing closed but having all its contents still active. If you continue to open and close repeatedly, the app will eventually eat up more memory until terminated by the OS.

Not surprisingly, Alloy and Titanium are good at cleaning up in most cases. A typical app created with Alloy can take care of cleaning up after closed windows and destroyed views automatically.

I’ve seen developers write code that iterates through all the controls in a window when it’s closed, and explicitly remove and nullify every object and reference, hoping that this will clean up the window correctly. In most cases, Alloy will take care of all this for you. However, there are some situations where Alloy can’t or won’t clean up for you. So, here’s a list of scenarios I tested to check if and how Alloy will handle releasing memory:

  1. Creating a global event handler within a controller that interacts with something in the controller
  2. Data-binding collections or models to an Alloy View
  3. Passing pointers to objects to external files like modules or libraries

In order to test all this, I created a really simple app which I describe below, and I’m running Xcode Instruments to test opening and closing windows and checking that objects are being released correctly.

A result of PASSED means that instruments showed that objects were created and destroyed successfully.

A result of FAILED means that instruments showed that objects were created and NOT released when the window or view was closed / destroyed.

Test 1 — PASSED — In this test, I’m opening a new window (a Login window showing a username, password and login and close button) and on clicking login or close, I’m using $.getView().close() to close the Window.

The login window was opened and closed and all objects were deleted on close and memory freed up — the pointer to the controller wasn’t nullified — I simply called .open() to open it and .close() once the user clicked login or close.

Test 2 — PASSED — In this test, I’m doing the same as in Test 1, but I’m adding some local addEventListeners on objects (instead of using the onClick handlers in the view XML) — again I’m closing with $.getView().close().

A common misconception is that if you don’t use the Alloy XML tag event handlers like onClick etc. and instead create them manually in the controller using addEventListener, they won’t be cleaned up for you. In reality, they are all cleaned up correctly regardless of whether they are defined in JavaScript in the Controller or XML in the View.

Test 3 — FAILED — In this test, I’m adding a global app event handler in the new window using Ti.App.addEventListener

So, in this example, I’ve put an arbitrary global event listener in the login window and because I’m not cleaning it up by removing it, the login window, despite appearing to close, remains in memory which means all its controls and elements remain too. If I repeat the process to open a new login window, I noticed doubling up of the elements in instruments which were never cleaned up.

Test 4 — PASSED — Same as Test 3 but I removed the global event handler when closing the window.

With the global event handler removed before closing the window, the window is properly closed, all elements and event handlers removed and memory released.

Test 5 — FAILED — Same as Test 3 but using a backbone handler.

Another common misconception is that using backbone event handlers is better than traditional addEventListener because they are cleaned up for you. This is not the case and adding this does prevent the window and its children being properly destroyed.

Test 6 — PASSED — Same as Test 5 but we’re using .off() to turn off the backbone event when closing the window.

Repeating Test 5 but using .off() to remove the backbone handler everything is released correctly.

Test 7 — FAILED — In this test, I’m opening a new window called “colors” which displays a list of models in a data-bound collection

I’m using backbone data binding via RESTe to bind an array of data to in this window and by not removing the data bindings the window is not closed properly.

Test 8 — PASSED — this is the same as Test 7, but I’ve added removing data bindings when the window is closed.

Issuing a $.destroy() before closing the window takes care of cleaning up backbone which means everything is removed correctly.

Conclusion

So, there are three key takeaways from all this to reduce the possibility of memory leaks:

  1. Firstly, there’s no need for a complex clean up process when closing windows. You don’t need to remove local event handlers, or start iterating through and removing child controls, and then nullifying everything. Just use $.close() and Alloy will clean everything up for you.
  2. If you’re using any global event handlers (built-in or via backbone) and you can’t avoid them completely, then ensure you use Ti.App.removeEventListener or $.off() to remove them when closing the window or removing the associated view.

  3. If you’re using any kind of alloy data-binding then use $.destroy() before closing the window or removing the view to make sure any bound collections and models (and their associated events) are removed.

You can also improve the way you’re creating and interacting with windows and views by implementing Alloy method chaining. As explained in this blog post, you’ll be able to create controllers and open views without using variable pointers.

Finally, if you download AlloyXL to your app/lib folder and add require(“alloyxl”); in your Alloy.js file, you’ll get a bunch of new abilities associated with creating controllers, and it’ll automatically add $.destroy()when closing windows.

AlloyXL adds some new capabilities to Alloy:

  • open, postlayout, close events for controllers (normally for views) so you can chain controller methods together and add code to react when a controller is opened.
  • a new once event associated with controller that fires once and then cleaned up.
  • $.destroy()automatically run whenever a window is closed.
  • an automatically managed Alloy.Controllers stack of open controllers, allowing you to access them directly without creating additional pointers.

For example:

$.getView().openWindow(Alloy.createController(“subview”).on(“open”, function(){
Alloy.Controllers.subview.getView().setBackgroundColor(“red”);
}).on(“doSomething”, function(){
// handle the doSomething here
}).once(“twoTime”, function(){
// this event will only ever fire once
}).getView());

In the above, no pointers are created in your code (other then Alloy.Controllers) and yet you are able to fire code when a controller’s view is opened; whenever $.trigger(“doSomething”) and once when $.trigger(“runOnce”) is executed from the controller.

What tips do you have for memory management? We’d love to hear them in the comments.

Code Strong!

1 COMMENT

Comments are closed.