Tech News Back Issues Issue: 062305
Providing Global Resources in Multiple Task Applications
This article continues our exploration of the use of tasks in Omnis Studio. In previous articles, we have discussed what task instances are and how they can be used to manage aspects of our applications. We have also seen how Omnis Studio deals with changes of context as either the user or the execution of our methods switches from one task instance to another. We have learned the difference between the current task and the active task and have seen how to make certain instances within a task local to that task. We have even learned that we can launch a new instance into a task from a method executing inside a different task instance, indicating that there is perhaps more flexibility and power at our disposal than we might have imagined at first glance.
So let's assume that we have decided to use multiple tasks within an application. Now we are faced with the problem of how to provide various application resources that can be accessed from anywhere within the application. In a single task application, this is easy to do - we can create task variables that can be accessed from within any instance contained in the task. But now we must find a way to share such resources across task boundaries.
What Do We Mean By "Global Resources"?
Global resources are items that we may need to access from anywhere
within our application. For example, Omnis Studio provides many
global resources for us: The date and time variables (
Most applications we build will have similar resource needs that are more application specific and that could not have been anticipated by the people who created Omnis Studio. An application with even rudimentary security will need to keep track of certain information about the current user and their current session within the application, for example. Access to SQL data sources may be through a session object that we desire to make global. There may be many custom functions that are required in diverse parts of the application for performing common calculations or other operations. The question is: How do we provide such resources so that they can be shared among the instances of our application, regardless of which task is the current or active one?
Inheritance of Task Superclasses
A partial solution can be found by using a "master task" superclass containing such resources. We could create this superclass to contain all our global methods directly or to contain task variables of object type that hold collections of such methods. We could also include other task variables that we may need to access "globally" as well, such as custom "constants" for certain calculations (such as an interest rate or engineering factor) or "states" that are specific to the current runtime (like the path to a local output directory). Then the task classes for our application could be subclassed from this superclass and all of them will contain the same methods and variables automatically.
But there is a problem with this approach. Yes, all these tasks contain the same variables and methods, inherited from the superclass, so they can be accessed from any location in any instance with the same code. But the variables of each task are completely independent of their counterparts in the other tasks. The subclasses do not share the task variables of the superclass, but simply make copies of them - actually, only copies of their definitions, so assigned values (other than default calculations) aren't even copied. If we chose to house most of our custom functions in object classes for the flexibility and portability they offer, then we have created multiple copies of these methods as well. Variable values contained within these object instances are not shared either, so changes made to such values inside one task instance are not automatically reflected in other task instances. The inheritance technique only gets us part of the way to a solution.
A partial solution to this problem exists. We could create our resources in a single task instance (perhaps the startup task), create a notational reference to this task instance and then pass that reference to our other tasks. This will work for many cases, but it limits our flexibility. There could still be scoping problems with this approach, for example. Still, for Omnis Studio 3 and earlier, this can be used effectively.
But users of Omnis Studio version 4 have another option...
Object Reference Variables at the Task Level
Object instances are wonderful for use as variable and method repositories. In fact, these combinations of variables and methods can be very useful for retaining and managing data (like a security object that can retain current user information and create new user tracking records for significant events we wish to monitor). But passing objects around can be a cumbersome chore. We all soon found that a more flexible technique for working with objects was needed for many cases.
Omnis Studio version 4.0 introduced the object reference data type. While object variables contain an instance of an object class, object reference variables only point to an instance of an object class. This is an important distinction. When an object variable is passed as a parameter, a new object instance is created which is a duplicate of the instance contained in the original object variable whose value was passed. That object then goes on to have a completely independent life of its own. When an object reference variable is passed as a parameter, the new object reference variable points to the same object instance as does the original object reference. No new object instance is created. That is, there remains only one object instance no matter the scope of either of the object reference variables. But how do we get this reference into an object reference variable in the first place?
An object reference variable generally comes into being without containing a valid reference (unless it is a parameter variable and is passed a valid reference as it is spawned). So it must be assigned a reference, either from another object reference variable or from a method that launches a suitable object instance and returns a reference to it. Only references to an object instance launched in a certain way can be assigned to an object reference variable. (That is, we can't use a reference to an object variable because it could easily go out of scope.) A little background information would be useful here...
Object Reference Basics
We will present a more thorough treatment of object references in a future article, but here is some basic information on how they are populated. Object classes in Omnis Studio version 4 and later contain a number of methods relating to their use with object reference variables. The method in which we are interested here is the $newref() method. When applied to an object class, this method launches a non-scoped instance of the object into the current task and returns a reference to that instance suitable for use in an object reference variable. We can populate the object reference variable in this way:
Parameters can be passed to the $construct() method of
the object instance within the parentheses that follow the name
of the method. Notice that we are not given options for assigning
a name to the object instance. It certainly has a name (as do all
instances within Omnis Studio), but we have no control over it because
we generally access such instances through object reference variables.
By default, Omnis Studio assigns the first instance of a given object
class that is launched into a task by the $newref() method
the same name as its class name (assuming that name has not yet
been used). If subsequent instances of that same object class are
launched into the same task instance while the first object instance
still exists, each is given a name consisting of the class name,
an underscore character and a randomly assigned integer (e.g.,
We can pass an object reference as a parameter to another object reference variable in the target method and that variable will also refer to the same object instance. If we need to pass that reference to a more "permanent" variable, like an instance variable, we can do so using a simple calculation:
We can also assign this reference to a cell in a list variable and all manner of other useful things, but those are subjects for another time. We have plenty of work to do here on our multiple task situation...
Setting Up the Task Superclass
So it would seem that all we need to do is to create the object reference variables we need in our task superclass, include code to populate them with an object instance (as outlined above) in the $construct() method of that task and then create all the task classes we need as subclasses of that superclass. Well, it's not quite that simple.
The trick is to create the new reference once in the first task to launch (the startup task) and then pass that reference to each new task that we instantiate. Setting up the task variables in the superclass is fine. That way we will have consistently named task variables guaranteed! But if we were to populate those variables using the $newref() technique in the superclass $construct() method, we would end up with multiple, independent instances of our object classes - just what we want to avoid! Does this mean this technique is another dead end? What options do we have?
There are at least two viable options. In the first case, we could simply override the $construct() method in each subclass and perform this operation only in our startup task. Then whenever we launch a new task instance, we can pass the necessary object references as parameters to the $construct() method of that instance. In the (overridden) $construct method of each task launched in this manner, we would then assign the reference held in this parameter to the appropriate task variable. Each of those will point back to the original instance launched in the startup task.
A second option is to also create a method in the task superclass named something like $setup() or $configure() and invoke that at some point within the $construct() method, passing appropriate parameters on to this subroutine. This is a little more involved, but it may appeal to some Omnis Studio programmers. That subroutine would then be overridden in the subclasses so that specific method lines could be included to address the configuration needs of each task.
I use a combination of these techniques in my own work. I use the first technique for establishing the object reference task variable(s) and the second technique for a number of other configuration operations. So my task superclass contains:
Here is the code for the $construct() method of the task
superclass (which I named
Not much to it, really. The real tricks are in the parts that we override in the subclasses.
Setting Up the Task Subclasses
The first subclass to create is the startup task. We can simply
use the default that was created when we first made our library
and set its superclass property value to
We next need to establish the object reference variable value and the object instance that it points to for each such task variable. For this task, we will do this in the $setup() method. The $setup method would then contain:
Since the object instance is created in code executed within the startup task, that instance belongs to this task. This implies that the startup task must remain in existence for this object to continue to exist. I only mention this because I have seen "techniques" where the programmer launches another "main" task for the application and then closes the startup task. This is entirely unnecessary, but if a programmer feels that it is, then I would suggest not bothering to perform the operations here until the "main" task is established.
In most multiple task applications I create, I launch the various task instances for the application at startup. This way, the user only has to switch from one task to another and I don't have to repeatedly establish and destroy the resources for any specific task. But it is equally viable to launch a task when it is needed and then close it again when the user is finished working in that area. The technique I demonstrate here for launching them all at startup can be easily modified to accommodate other styles of programming.
Either the $construct() method or the $setup()
method of the startup task can be used to launch the other task
instances of the application. As always, there are two techniques
that we can use to do this: the method command technique
and the notational technique. Suppose that our application
contains two tasks in addition to the startup task:
Each of these method lines will launch a task instance with the same name as the class from which it was spawned. Notice that the first parameter of the notational method $open() is reserved for the name we wish to assign. This is one of the rare parameters that can be left absolutely empty in Omnis Studio. We can instead put an empty string (two quotes with nothing between them) as the value for this parameter and get the same effect. This tells Omnis Studio to use the class name as the name for the task instance. The second parameter for the notational method is the same as the first parameter for the command method. It is the first parameter that will be received by the $construct() method of the task instance.
This means that we must add a parameter variable of object reference
type to the $construct() method of each task for each such
variable we wish to pass from the startup task. (Our example here
has just the one, but we could very well decide to have more in
a real application.) I have used the name
This assures that the reference to the original object instance is passed to our object reference variables in each task rather than establishing new object instances. The beauty of this is that any reference can be passed since they all point back to the same object instance. So it is not necessary to launch the other tasks all from the startup task, but only from a task that has received a reference that originated from there. The most important rule is that the startup task instance (or whichever task contains the object instance) must remain open for the reference to be valid.
Checking Our Handiwork
Once our application has been opened with this code in place, we should be able to verify that we have one, and only one, instance of our object class. We can do this in our development copy of Omnis Studio using the Notation Inspector.
It is important to understand that there is no "iObjects" group at the root level as there is for other types of instance. Neither can we drill down through the iTasks group to see the object instances contained within a task (which would only be the "non-scoped" instances we discussed in this article anyway, since those associated with object variables would not be contained so directly within a task instance). So where would we find this information?
We have to instead drill down through the $libs node, the library that contains the original object class, the $objects group within that library, the object class itself and finally the $insts group for that class. There we will see all the instances that are suitable for use with object reference variables. Note that we will not see object instances used with object variables in this listing. It may be that they are out of scope or it may be that they just aren't included in this prestigious group. Whatever the reason, we only see object instances launched by $newref() here.
This listing gives us a means of ascertaining whether we have inadvertently launched additional instances rather than passing references as we intended. It also lets us test to see whether we have successfully closed such an instance... but that is a topic for another time.
An expanded version of the example library I posted on my web site
for the previous article will be available once this article is
published. It includes some alternate lines of code that allow you
to test launching multiple object instances instead of passing references
that point to the original object instance. Also note that the
Hopefully you will find other useful links there as well.
In the next article, the final one on tasks for now, we will examine the process of closing a task instance. There are some interesting and useful twists to this process that make it worthy of closer scrutiny.
In other news, if you happen to be attending the Macworld Conference and Expo in Boston, MA, come look me up. I am presenting a one-hour session entitled Enterprise Database Systems on Mac OS X at 6:00 PM on Wednesday, 13 July and this session is open to all attendees of the Expo. (The expensive "conference" pass is not required.) As one might imagine, Omnis Studio will be featured prominently, but the presentation is more about the challenges to be faced in the enterprise market for Macintosh consultants than it is about specific pieces of software. There is also an on-going Meet the Speakers session on the show floor, but I have not yet been told my time slot for this public relations event.
Also, the third article in a series for MacTech magazine is nearing completion. The first two were in their March and May issues and there should be at least two more in June and July. Publication has lagged a bit from the real world calendar, but it is nice to be able to represent Omnis Studio in a trade publication of any sort - and these people have become very enthusiastic about Omnis Studio from reading my articles for editorial purposes.
Copyright of the text and images herein remains with the respective
author. No part of this newsletter may be reproduced, transmitted,
stored in a retrieval system or translated into any language in any
form by any means without the written permission of the author or
Omnis® and Omnis Studio® are registered trademarks, and Omnis 7 is a trademark of Raining Data UK Ltd. Other products mentioned are trademarks or registered trademarks of their corporations. All rights reserved.