Understanding Alloy Controller and View Lifecycle

MVC Design in Alloy

In Alloy, each screen or component is typically encapsulated in a controller (.js), an XML view, and an optional TSS stylesheet. Alloy compiles these into JavaScript at build time. Controllers persist in memory until explicitly released, which, if not done properly, leads to retained event listeners and zombie views.

Memory Behavior in Titanium Runtime

Titanium (the underlying runtime) does not include automatic garbage collection for UI components like views or controllers with open event listeners. Manual cleanup is required to avoid memory bloat or context leaks during navigation or app state changes.

Common Symptoms

  • Increased memory usage with each navigation (especially between complex views)
  • Duplicate event handling or inconsistent UI behavior (e.g., multiple taps triggered)
  • Out of memory (OOM) crashes on Android or older iOS devices
  • UI elements not reflecting model changes after reopening a window
  • Unexpected reference errors when reusing Alloy controllers

Root Causes

1. Unreleased Event Listeners

Event listeners (e.g., on click, scroll, model change) tied to views or models remain active if not manually removed when closing a controller or window.

2. Improper View Detachment

Calling $.getView().close() closes the window, but child views and controller references may remain in memory if not explicitly dereferenced.

3. Global References to Controllers

Storing controller instances globally (e.g., in Alloy.Globals) for reuse leads to circular references and memory leaks unless destroyed properly.

4. Model Binding Without Unbinding

Backbone model bindings (e.g., model.on('change')) persist unless off() or destroy() is called explicitly on view destruction.

5. Android-Specific View Caching

Due to the Titanium runtime, Android caches certain views or fragments more aggressively than iOS, making proper disposal crucial.

Diagnostics and Debugging

1. Monitor Memory via Instruments / Android Profiler

Track heap usage before and after navigating between views. Retained controllers or views suggest leakage.

2. Log Controller Lifecycle

Ti.API.info('Controller created');
Ti.API.info('Controller destroyed');

Log controller creation and use Alloy’s destroy() method to track release points.

3. Use Ti.App Memory Warning Event

Listen for Ti.App.addEventListener('memorywarning') to capture high usage scenarios and profile where leaks occur.

4. Use Debug Mode and Object Count

Use __ALLOYID0 count in debugger or console to monitor lingering view/controller instances.

5. Audit Alloy Models and Collections

Ensure you are unbinding listeners on collections/models when views close. Failure here often causes data mutation bugs after reuse.

Step-by-Step Fix Strategy

1. Explicitly Destroy Controllers on Close

$.getView().addEventListener('close', function() {
  $.destroy();
});

Ensure all event listeners and model bindings are removed when the view is closed.

2. Remove All Listeners in Teardown

In destroy() or onClose, remove all events manually using off() or removeEventListener().

3. Avoid Global Controller Instances

Use local controller scoping and lifecycle hooks to manage reuse instead of global references. Dispose or recreate as needed.

4. Detach and Nullify Views

Set view references to null after closing. For example: $.myLabel = null;

5. Defer Heavy View Creation

Use lazy-loading patterns for subviews or tables. Don’t initialize views until needed to reduce load and simplify teardown.

Best Practices

  • Use $.destroy() in all windows with dynamic content
  • Use a base controller pattern to enforce cleanup in reusable modules
  • Avoid global models unless truly shared application-wide
  • Implement a consistent onOpen/onClose lifecycle pattern
  • Test navigation flows on low-memory devices before release

Conclusion

Appcelerator Alloy enables modular, maintainable mobile app development, but requires manual lifecycle management for high performance. Event listeners, view references, and model bindings must be explicitly cleaned up to prevent memory leaks and UI inconsistencies. By adopting disciplined patterns for controller teardown and resource disposal, teams can build Alloy apps that are stable, scalable, and performant across iOS and Android platforms.

FAQs

1. Why does memory usage increase after each view navigation?

Because controllers and their views aren’t destroyed. Use $.destroy() and nullify references.

2. Is Alloy memory management automatic?

No. You must manually clean up controllers, views, and event listeners.

3. How can I track which views are leaking?

Use memory profilers or log Ti.API.info() on create and destroy for each controller.

4. Should I reuse controllers by keeping them in Alloy.Globals?

Not unless you manage their lifecycle carefully. Reuse often leads to memory leaks.

5. What’s the best way to clean up Alloy models?

Call off() to remove bindings and ensure any event-based listeners are detached during destroy().