For the last three years or so, I have primarily been a Flash/Flex/AIR actionscript programmer on museum kiosks. Applications are always completely custom, large in scope, and must run without user interaction for indefinite periods of time. This week I have been focusing on trying out new programming and architecture techniques and realized that I use a method for object destruction and prevention of memory leaks that others my find interesting. When I first started programming kiosks; I had an issue with a serious memory leak that heightened my paranoia to a new level. Since following this paradigm, I have not had any issues with memory leaks.
The first portion is adding a public function to each applicable, non-static class that you always call right before setting the class instance to null. This function ensures all objects within the target class are destroyed and any internal event listeners are destroyed. In my applications it normally looks like this:
public function destroyInternals():void {
destroyObject1();
destroyObject2();
destroyObject3();
}
Then in my destroyObject functions I have all the specific code related to completley destroying each dynamic object. As the class grows, I just continue to add destruction functions to my master destroyInternals function.
The second technique that goes along with the paradigm has to do with the way objects are created and destroyed. Whenever I create a complex object inside a class, it gets two functions; a creation and destruction function, with an automatic callback. These functions essentially check if the object is null or not. If the object is not null, it destroys the previous instance prior to creating another. Here is an example using the
Socket class.
protected function createSocket():void {
if (_theSocket == null) {
_theSocket = new Socket();
_theSocket.addEventListener(Event.CONNECT, handleConnection, false, 0, true);
_theSocket.addEventListener(Event.CLOSE, handleDisconnect, false, 0, true);
_theSocket.addEventListener(IOErrorEvent.IO_ERROR, handleSocketIOError, false, 0, true);
_theSocket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, handleSocketError, false, 0, true);
_theSocket.addEventListener(ProgressEvent.SOCKET_DATA, handleSocketData, false, 0, true);
_theSocket.connect(_targetServer, _targetPort);
} else {
destroySocket(true);
}
}
protected function destroySocket(_OptionalCallback:Boolean = false):void {
if (_theSocket != null) {
try {
_theSocket.close();
} catch (e:Error) {
}
_theSocket.removeEventListener(Event.CONNECT, handleConnection);
_theSocket.removeEventListener(Event.CLOSE, handleDisconnect);
_theSocket.removeEventListener(IOErrorEvent.IO_ERROR, handleSocketIOError);
_theSocket.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, handleSocketError);
_theSocket.removeEventListener(ProgressEvent.SOCKET_DATA, handleSocketData);
_theSocket = null;
if (_OptionalCallback == true) {
createSocket();
}
}
}
This technique may be seen as excessive coding; but provides a good double-check architecture in aggressive and challenging situations. For experienced programmers, a singleton pattern would probably accomplish similar ends in a lot less code!