Retain/Release in Omnis
As I was bragging about this on the Omnis list let's start doing a writeup about this. This will be a multipart writeup on object memory management in Omnis Studio 4 using object references.
This part we'll look at a simple retain/release object base class that allows us to track a single instance of an object that is being used by different parts of the system. It is a widely used model though my primary inspiration has been the use of it in Cocoa.
For the second part we'll look at implementing a copying scheme for our objects.
For the third part we'll look at auto releasing objects, why we need to do this and how we can implement this in Omnis.
The final part we'll look into tracking retains and releases in order to find memory leaks (not just leaks happening but finding the source).
From the earlier versions of Omnis Studio you could create simple objects and instantiate them through object variables. TigerLogic strayed from the competition in their attempt to keep memory management simple and hidden for the user. As long as the object variable remained in scope the object would be instantiated. When it went out of scope the object would be destructed. When properly used it was a really easy to use model.
The thing got confusing when you started passing objects around. Basically an object got copied unless you stuck to using item or field references to an object variable but field references are limited and it isn't always possible to get an item reference to a variable. It got especially hairy when you started putting objects into lists.
The end result was dozens of copies of your object floating around sometimes having state changes copied back into the original object but more often having each copy of the object lead its own life. This was also the reason we never had a destruct method on objects as when would this be called with so many copies floating around?
The idea behind it was good and with a little more control Omnis could have had a perfect Object model that would only copy an object when this is wanted and to nicely track each reference to an object and destruct the object when no more references existed. Instead TigerLogic went down the path of most of the other languages and gave us Object References which are basically pointers. Now don't get me wrong, these are absolutely cool and have some neat tricks up their sleeves that make them much safer to use then say pointers in C(++) however you need to manage them yourselves just like in a language such as C(++). TigerLogic could have given us the best of both worlds. C'est la vie, we always find a way:D.
An example, I have a window that allows me to manage some data I have retrieved from a database and which I now contain within an object. When I am finished with this window and close it, it nicely cleans up and destroys my object.
But what if in the process of using my window this window has opened another window and has told this window about the object containing the data by giving it the reference to this object? The other window simply performs a different function on the same data then my initial window does (say it displays the data in a graph instead of letting me modify the data as the first window allows me to).
Now if the other window has to close before the user can return to the first window there is no issue but interfaces often do not want to lay that restriction on the user so the user may be able to switch between the two windows and deside to close the first window (i.e. I may want to keep my graph window open but no longer need to edit the data).
The result is that when the first window closes it destructs the object and the second window which is still open suddenly finds itself with an invalid reference and no access to the data it is displaying (I may want to change settings to display the graph differently).
So we come to manual reference counting. Reference counting in its simplest form means that whenever you copy the reference to an object into another variable you tell the object there now is an extra reference and whenever you are done with the object, you tell the object you are no longer referencing it. The object simply keeps track of a counter and when the counter hits 0, the object gets destructed.
As such a developer never tells the object to destruct itself, he/she only informs the object when he/she is done with it.
So back to our example our first window still instantiates the object. Automatically the object gets a reference count of 1 as whomever instantiates an object must be referencing it. When our window closes instead of destructing the object it calls a release on the object. This release method decreases the reference count. It will return to 0 and the object gets destructed.
If however we open our second window and parse the reference to our object to it our second window needs to tell our object it now also references it. If we know our window will close before we ever get back to our first window we may omit this but if the second window works independend of the first we perform this step by calling a retain on the object. The retain simply increases the reference count by 1. Can't be simpler then that. Obviously if our second window retains the object it must also call the release method when it is done with the object.
Now if our first window closes before our second window we do not have a problem. The first window will call release but since our release count is now 2 as two windows reference the object the release results in the reference count going down to 1.
When the second window closes it calls release on the object bringing the reference count to 0 destructing the object.
The implementation of our base class object up to this point can't be simpler. The only important remark at this point is that this approach does not allow mixing object variables with object references. If an object is subclassed from this base class it should always be used with an object reference.
instance variable ivRefCount (kInteger)
oBaseRetain.$construct
----
Calculate ivRefCount As 1 ;; Could be a default :)
oBaseRetain.$retain
----
Calculate ivRefCount as ivRefCount + 1
oBaseRetain.$release
----
local variable lvObjRef (kObjRef)
Calculate ivRefCount as ivRefCount -1
If ivRefCount = 0
Calculate lvObjRef as $cinst.$objref()
; Warning, if you are stepping through this code, the method editor will give a you
; a warning about the instance of the code you are stepping through no longer existing...
do lvObjRef.$deleteref() ;; Bye bye object...
End If
To actually implement our base class we subclass it and make sure we call $inherited.$construct() in the construct of our subclass. We can't live without it. Let's draw up some skeleton code for our two windows from our example. oDataObject is our object class containing our data that we have subclassed from oBaseRetain. Let's start with our first window:
instance variable ivDataObject (kObjRef)
wFirstWindow.$construct
----
Calculate ivDataObject as $objects.oDataObject.$newref(...)
; Do whatever other initialisation we need to do...
wFirstWindow.$destruct
----
If ivDataObject.$validref ;; You can never be to safe...
Do ivDataObject.$release() ;; We no longer want it
End If
wFirstWindow.OpenBtn.$event
----
On evClick
Do $windows.wSecondWindow.$open('*',kWindowCenter,ivDataObject)
Quit event handler (Pass to next handler)
And then our second window:
instance variable ivDataObject (kObjRef)
wSecondWindow.$construct(pvDataObject)
----
Calculate ivDataObject as pvDataObject
Do ivDataObject.$retain() ;; let the object know we want to hang on to it
; Do whatever other initialisation we need to do...
wFirstWindow.$destruct
----
If ivDataObject.$validref ;; You can never be to safe...
do ivDataObject.$release() ;; We no longer want it
End If
Can't be much simpler then that :D
Obviously both windows now use the same object instance and as a result share everything between them. That means that if our first window make a change to the object this also influences our second window. In our graph example this is really great because as I modify my data in my first window the second window can immidiately show the changes in the graph.
However our second window may not be happy with this and may want to use a copy of our object instead as it no longer want to have acces to changes to the object. Simply copying the data held within an object byte for byte may not lead to the desired result and we need to make sure our reference mechanism stays intact. This is the subject of our next section.
Add to: Digg | Technorati | del.icio.us | Stumbleupon | reddit | Furl
Developers can use the Omnis web client to build all types of web applications and rich Internet applications by presenting a highly functional interface in the user's web browser or on mobile devices. The business logic and database access in such web and mobile applications is handled by the Omnis server.