TweetFollow Us on Twitter

December 95 - Speeding Up whose Clause Resolution In Your Scriptable Application

Speeding Up whose Clause Resolution In Your Scriptable Application

Greg Anderson

The Object Support Library provides convenient mechanisms for scriptable applications to support complex expressions that may return multiple results (such as every item of container "b" whose name contains "a"). However, the performance of applications that rely on the default behavior is nowhere near what it could be if the application took on some of the work itself. This article shows you how to gain ten- to a hundred-fold increases in the performance of whose clause resolution in your scriptable application. If your application is not yet scriptable, you'll find that the foundation classes presented in this article do most of the work required to support scripting.

One of the greatest strengths of AppleScript is its built-in ability to do complex operations on groups of objects in a single line of script. For example, suppose you have a set of shapes in a scriptable drawing program, and you'd like to change the color of all the red shapes to green. In conventional programming languages, you'd need to write a loop that iterates over each object in the set, tests to see if its color is red, and then does a "set color to green" command for each red object that was found. Using AppleScript, you can do the same operation with the single statement set color of every shape whose color is red to green. In that statement, every shape whose color is red is called a whose clause, and it's the inclusion of whose clauses that makes AppleScript the powerful language it is.

You may at first doubt that using a whose clause is much better than writing the equivalent script with a loop. After all, the direction of modern processor design has been toward simplicity of the instruction set; RISC chips are able to gain incredible performance improvements by doing optimizations that aren't possible in CISC chips. Also, when all is said and done, the whose clause must finally execute the same loop-and-compare algorithm that you'd be forced to use if you wrote the script with the basic flow-of-control script commands, such as do-while and if-then.

Using a whose clause is, however, much more efficient than the alternative. AppleScript is based on the client/server paradigm: typically your script, the client, will be running in one application (usually the Script Editor or a script saved as a miniapplication), with the application being scripted acting as a server. In this situation, each script command that's directed at the scriptable application needs to be transferred between the two applications. A whose clause is a single script command, but with the loop approach many commands would need to be sent. Furthermore, AppleScript allows the scriptable application to reside on a different machine than the application running the script; if your script is running on a machine in Cupertino, California, and the server is on, say, Mars, reducing the number of round-trip messages would have a profound impact on the performance of the script. Remember, you can currently get only about 30 round-trip Apple events per second, so even if you aren't sending data to Mars, you'll still do a lot better with fewer events than with many.

There's another, similar reason that using whose clauses is superior to the equivalent loop-based script: AppleScript compiles scripts into byte codes that are interpreted during execution, whereas the individual script commands (once interpreted) are processed by a scriptable application typically written in a language that's compiled into machine code (be it 680x0 or PowerPC(TM)). The loop-and-compare script will execute several lines of script for every item that's compared, whereas the whose clause is but a single line of script that triggers processing in a compiled application. It should be quite clear which will take less time to execute.

The Object Support Library (OSL) -- the library that provides the API you use to make your application scriptable -- enables your application to support whose clauses without requiring you to write a lot of additional code. You only need to provide an object-counting function and an object comparison function, and the OSL can resolve whose clauses for you. Since supporting whose clauses allows script writers to write more efficient scripts, you should always do at least this much. However, there are two other features of the OSL that can vastly increase the performance of scriptable applications but are often ignored by application writers: whose clause resolution (a way for your application to find the objects that match a whose test without using the OSL) and marking (a mechanism for efficiently handling collections of objects, such as those satisfying a whose clause). Using whose clause resolution, with the help of marking, will enable you to get the most out of your scriptable application. Resolving whose clauses can be a bit tricky, but with a little help from this article, you'll be on your way in no time.

If your application is not yet scriptable, you'll find the sample code included with this article (and on this issue's CD) to be invaluable in getting you up and running -- particularly since it contains a lot of reusable code.


Good descriptions of the OSL can be found in the develop articles "Apple Event Objects and You" in Issue 10 and "Better Apple Event Coding Through Objects" in Issue 12. If you need a quick review of the OSL and you don't feel like putting down this issue of develop to dig through your back issues, read on. If you can already generate tokens and resolve object specifiers in your sleep, by all means skip ahead to the next section.

When AppleScript is processing a script command such as delete paragraph 2 of document "sample", it converts the command into an Apple event which it sends to the scriptable application that's referenced by the script. The Apple event's event class and message ID together specify the verb of the operation being performed -- in this case delete. The object being operated on is passed in the keyDirectObject parameter of the Apple event, which is called, naturally enough, the direct parameter of the event.

The direct parameter is almost always an object specifier -- a descriptor of type typeObjectSpecifier -- although in some cases it may be something else. For example, in addition to object specifiers, the Scriptable Finder accepts alias records and file specifications in the direct parameter of events sent to it. If the direct parameter of an event is not of type typeObjectSpecifier, you're on your own to convert it into some format that's understood by your event handler. For descriptors that are of this type, though, all you need to do is call the function AEResolve, and the OSL will step in and help your application resolve the object specifier -- that is, locate the Apple event objects it describes.

Object specifiers are resolved through object accessor callbacks that your application installs to allow the OSL to communicate with your application during object resolution. The accessor callbacks must take the description of the object requested by the OSL (for example, document "sample") and return a token that describes the object in terms that the application can understand (for example, a pointer to a TDocument object). Tokens are passed back to the OSL in an AEDesc, a structure that contains a 32-bit descriptor type and a handle. Your application has complete control over what it stores in the token, as long as the AEDesc is valid (that is, it was created with AECreateDesc).

When the OSL calls your application's object accessor callbacks, it always passes either a token that represents the containing object (which it got from an earlier call to one of your object accessors) or a representation of the default container of the application, which is also called the null container of the application. So, to resolve the object specifier paragraph 2 of document "sample", the OSL first asks for document "sample" from the null container. Then it asks the application to provide a token for paragraph 2 from the token the application provided in response to the request for document "sample". The token that the application provides for paragraph 2 is returned as the result of the AEResolve call; the application will presumably use this token to process the Delete event.

    Resolving object specifiers is explained in Chapter 6 of Inside Macintosh: Interapplication Communication. A figure illustrating the process of resolving object specifiers is on page 6-6.*


Inside Macintosh: Interapplication Communication describes marking as a mechanism whereby items to be operated on are marked with some flag during resolution (that is, from the callbacks made by the AEResolve function); then, during execution, each marked item is processed and the mark is cleared. As described, marking doesn't sound very interesting and appears to be useful only in fringe cases.

Marking is actually very well suited for use as a general-purpose collection mechanism whenever the OSL needs to group tokens together to process an object resolution. For example, if the OSL is resolving the whose clause every shape whose color is red and there are multiple red shapes, the result of the call to AEResolve must be a collection of all the tokens that represent red objects. If your application supports marking, the OSL asks your application to create a special mark token to represent this collection. After your application provides the OSL with a mark token, the OSL will ask your application to add the tokens it provided for the red shapes to the mark token's collection. When AEResolve completes, the mark token is returned as the result of the resolution.

If your application doesn't support marking, the OSL will create collections of tokens for you by copying the data from your tokens into a descriptor list (an AEDescList). It calls the standard Apple Event Manager routines for creating descriptor lists, which copy the data out of the data handle of the AEDesc and then store the token data somewhere inside the data handle of the descriptor list; the descriptor type of the AEDesc is similarly encapsulated.

Dealing with descriptor lists of tokens can be inconvenient, particularly if your application already supports collections of objects in some other way. The OSL marking mechanism gives you the flexibility to handle collections in any way that's convenient for your application.

To support marking, you must pass the flag kAEIDoMarking to AEResolve and implement the three marking callbacks that are passed to AESetObjectCallbacks: the create-mark-token callback (called just a "mark-token callback" in Inside Macintosh), the object-marking callback, and the mark-adjusting callback. The create-mark-token callback doesn't need to do anything more than create an empty mark token. The OSL will dispose of this token as usual by calling your token disposal callback when the token is no longer needed. Listing 1 shows an example implementation of a create-mark-token callback.

Listing 1. Create-mark-token callback

pascal OSErr CreateMark(AEDesc containerToken, DescType desiredClass, 
      AEDesc* markTokenDesc)
   TMarkToken* markToken;
   markToken = new TMarkToken;
   markTokenDesc->descriptorType = typeTokenObject;
   markTokenDesc->dataHandle = markToken;

   return noErr;
The object-marking callback is passed a mark token created from the create-mark-token callback and some other token created by one of your application's object accessor callbacks. Your object-marking callback should add a copy of the other token into the mark token (or apply a reference count to the token being added), because the OSL will dispose of the token added to your collection shortly after calling your object-marking callback. Listing 2 shows one implementation of an object-marking callback.

Listing 2. Object-marking callback

pascal OSErr TAccessor::AddToMark(AEDesc tokenToAdd, AEDesc
      markTokenDesc, long markCount)
   AEDesc      copyOfToken;
   TMarkToken*   markToken;

   // We know that the OSL will only give us mark tokens created with 
   // our create-mark-token callback, but real code would do a test 
   // before typecasting.
   markToken = (TMarkToken*) markTokenDesc.TokenObject();
   // Add a copy of the token to the collection, because the OSL will
   // dispose of tokenToAdd after passing it to you. A reference-
   // counting scheme is good here.
   copyOfToken = CloneToken(tokenToAdd);

   return noErr;
The mark-adjusting callback is called to remove ("unmark") tokens from the collection. Oddly enough, its parameters specify which tokens in the range to keep; all tokens outside the specified range should be discarded.

Implementing the marking callbacks is trivial. The only real work involved in supporting marking is handling collections of tokens when they're ultimately received by one of your event handlers (handling Move events, for example). The amount of code required to handle the marking callbacks and maintain your own collections is minimal; in fact, the time you'll save by not having to hassle with descriptor lists of tokens will more than make up for the implementation cost. You'll find more information on handling collections of tokens later in this article. Don't put off marking as an optimization to be done later; incorporate it into the design of your application from the very beginning.

    For more details on the marking callbacks, see Inside Macintosh: Interapplication Communication, pages 6-53 to 6-54.*


The only thing that a scriptable application needs to do to support whose clauses is provide an object-counting function and an object comparison function -- the OSL will do the rest of the work. When the OSL does a whose clause resolution, however, it has no choice but to iterate over every element in the search set, repeatedly calling your application's object accessor, object comparison, and token disposal callbacks. Huge performance gains can be realized if you resolve whose clauses yourself, because you'll avoid the overhead the OSL requires to make these callbacks.

Passing the flag kAEIDoWhose to AEResolve tells the OSL that you'll resolve the whose clause yourself. The OSL calls your object accessor with the key form formWhose (see Listing 3). The key data is a whose descriptor -- that is, an AERecord that describes the comparison to be performed in the search. Your application should interpret the whose descriptor and test every element of the container token to see if it matches the specified criteria. If the whose descriptor is too complex for your application, you can return the error code errAEEventNotHandled from your object accessor, and the OSL will do the resolution for you with the default techniques. This is very useful, as it allows you to maximize the performance of the most common whose clauses, yet still support complex whose descriptors that are likely to be encountered only rarely.

Listing 3. Handling formWhose in the object accessor

pascal OSErr MyObjectAccessor(DescType desiredClass,
      AEDesc container, DescType /*containerClass*/,
      DescType keyForm, AEDesc keyData, AEDesc* resultToken,
      long /*hRefCon*/)
   switch (keyForm) {
      // case formAbsolutePosition, and so on
      case formWhose:
         // TWhoseDescriptor is a class that knows how to interpret
         // a whose descriptor and test tokens for membership in the
         // search set defined by the desired class and the whose
         // descriptor.
         TWhoseDescriptor whoseDesc(desiredClass, keyData);
         // TTokenIterator is a class that knows how to iterate
         // over the elements of a token.
         TTokenIterator iter(container);
         for (iter.Reset(); iter.More(); iter.Next()) { 
            AEDesc token = iter.Current();
            if (whoseDesc.Compare(token) == kTokenIsInSearchSet) {
               // Add token to the collection stored in resultToken.
               AddTokenToResult(token, resultToken);
   return noErr;
The astute reader will notice that the scheme presented in Listing 3 is very similar to the process that the OSL goes through to resolve whose clauses. There are still optimizations that could be made to speed up the resolution further, but we'll get to those later. To resolve whose clauses as shown in Listing 3, your application must be able to do the following:
  • Iterate over the elements of any token.

  • Determine class membership of any token.

  • Compare properties of the elements of any token.

  • Convert a whose clause into some internal representation usable by your application.
The first two operations are required of any scriptable application, so yours probably can already do them. Comparing properties is something your application probably doesn't do yet, but in the worst case you could always write a few lines of code that call your property object accessor function, retrieve the data from the resulting property token, and then compare the descriptor that was returned. Obviously you can do better than this in terms of performance, and later on we'll investigate how. First, though, we'll look at how to interpret the contents of a whose descriptor.


Earlier I claimed that a whose descriptor was an AERecord, but I lied. A whose descriptor is actually a descriptor of type typeWhoseDescriptor. Internally, a whose descriptor is stored just like an AERecord, but you can't extract its parameters unless you first coerce it to type typeAERecord. In Apple events parlance, this type of descriptor is called a coerced record; its basic type is typeAERecord, and its coerced type is typeWhoseDescriptor.

The advantage of coerced records is that they allow clients of the Apple Event Manager (for example, the OSL) to define new descriptor types for AERecords that define the context in which the record will be used and specify (by convention) what parameters the client can expect to find inside it. The disadvantage is that it requires an extra memory allocation to coerce the descriptor back to typeAERecord before the parameters of the coerced record can be accessed. This is unfortunate, as one of the primary goals of performance optimization is to remove extraneous memory allocations; coercing the descriptor back to typeAERecord is part of the current design of the Apple Event Manager, though, so there's nothing we can do about it.

There are two parameters inside a descriptor of type typeWhoseDescriptor: keyAEIndex and keyAETest.

  • The keyAEIndex parameter usually contains an enumeration whose value is kAEAll; this corresponds to the word every in the whose descriptor every item whose name contains "e". The other possible values are kAEFirst, kAELast, kAEMiddle, and kAEAny for whose clauses that request the first, last, middle, or any (random) item. The keyAEIndex parameter might also be of type typeLongInteger or typeWhoseRange, to indicate a single item or a range of items, respectively.

  • The keyAETest parameter contains another coerced AERecord whose type can be either typeCompDescriptor or typeLogicalDescriptor. In either case, you must coerce the descriptor to type typeAERecord to access the parameters inside it.
A comparison descriptor (typeCompDescriptor) contains three parameters: two objects to compare (keyAEObject1 and keyAEObject2) and a comparison operation to be performed on them (keyAECompOperator). Usually the first object to compare is a special type of object specifier that indicates a property to compare (for example, pName), and the second is a literal constant to compare it against (for example, "e"). The comparison operators include contains, begins with, ends with, equal, not equal, greater than, and a bunch of other relational operators. Because comparison descriptors can contain object specifiers (and usually do), they can become arbitrarily complex. You won't be able to resolve them all unless you reimplement the entire functionality of the OSL, at which point you might as well not call AEResolve either (thank goodness for errAEEventNotHandled, which allows you to fall back on the OSL if your application cannot parse a whose descriptor).

Fortunately, logical descriptors are much simpler than comparison descriptors. A logical descriptor contains two parameters: keyLogicalOperator and keyLogicalTerms. The logical operator indicates the Boolean logic to apply on the contents of the logical terms: and, or, or not. The logical terms descriptor is, as you may have guessed, a list of descriptors whose type is either typeCompDescriptor or typeLogicalDescriptor. Figure 1 shows the contents of a whose descriptor that corresponds to the script every item whose name contains "e" and size is 0.

    The contents of whose descriptors are described in Inside Macintosh: Interapplication Communication, pages 6-42 to 6-45.*

Figure 1. Contents of a whose descriptor


It may look like there can be a lot of different cases to handle in a whose descriptor, but it actually doesn't take too much code to convert a whose descriptor into a format that your application can understand. The next few listings show how this might be done. The code presented is somewhat simplified; it doesn't look at the keyAEIndex parameter of the whose descriptor (kAEAll is assumed), and it recognizes only very specific formats of comparison descriptors. Even this much of an effort is very useful, because it will cover about 90% of the whose clauses that your application is likely to encounter, and it's still possible to return errAEEventNotHandled and allow the OSL to take over for the rest. If you're expecting me to fall back on every develop author's favorite phrase, "This impossible task is left as an exercise for the reader," you're in for a surprise. The sample code on the CD will parse any valid whose descriptor passed to it and never falls back on the default handling provided in the OSL.

The top-level routine, ParseWhoseDescriptor, simply extracts the keyAETest parameter and passes it to ParseWhoseTest, returning the resulting search specification. These two routines are shown in Listing 4. (A search specification is an application-defined object that knows how to test tokens for membership in the search set defined by the whose descriptor; see the sample code on the CD for the implementation of the search specifications used in these listings.) ParseWhoseTest examines the type of the descriptor (either logical or comparison) and then extracts the appropriate parameters and passes them to either ParseLogicalDescriptor or ParseComparisonOperator, whichever is appropriate.

Listing 4. Interpreting the contents of a whose descriptor

      ParseWhoseDescriptor(TDescriptor whoseDescriptor)
   TAbstractSearchSpec*   searchSpec = nil;
   TDescriptor            testDescriptor;

   // Real code would call whoseDescriptor.GetDescriptor(keyAEIndex)
   // and at the very least check to see that its value is kAEAll,
   // and fail with errAEEventNotHandled if it isn't.
   testDescriptor = whoseDescriptor.GetDescriptor(keyAETest);
   searchSpec = ParseWhoseTest(testDescriptor);
   return searchSpec;

TAbstractSearchSpec* ParseWhoseTest(TDescriptor whoseDesc)
   TAbstractSearchSpec*   searchSpec = nil;

   switch (whoseDesc.DescriptorType()) {
      case typeLogicalDescriptor:
         TDescriptor   logicalOpDesc, logicalTerms;
         DescType      logicalOp;

         logicalOpDesc =
         logicalOp = logicalOpDesc.GetEnumeration();
         logicalTerms = whoseDesc.GetDescriptor(keyAELogicalTerms);
         searchSpec = this->ParseLogicalDescriptor(logicalOp,
      case typeCompDescriptor:
         TDescriptor   compOperatorDesc, obj1, obj2;
         DescType      compOp;

         compOperatorDesc =
         compOp = compOperatorDesc.GetEnumeration();
         obj1 = whoseDesc.GetDescriptor(keyAEObject1);
         obj2 = whoseDesc.GetDescriptor(keyAEObject2);
         searchSpec =
             this->ParseComparisonOperator(compOp, obj1, obj2);
   return searchSpec;
Since logical descriptor records can contain one or more terms, each of which is either a comparison or a logical descriptor record, ParseLogicalDescriptor calls back to ParseWhoseTest for each term in the record, creating a search specification for each (see Listing 5). If there's more than one term, ParseLogicalDescriptor compiles the resulting search specifications into a list and returns that; otherwise, it returns a single search specification for the single term.

Listing 5. Resolving logical descriptors

TAbstractSearchSpec* ParseLogicalDescriptor(DescType logicalOperator, 
      TDescriptor logicalTerms)
   TAbstractSearchSpec*   searchSpec = nil,
                        oneSpecification = nil;
   TDescriptor            oneTerm;
   TSearchSpecList*      specificationList = nil;

   FOREACHDESCRIPTOR(&logicalTerms, oneTerm) {
      oneSpecification = ParseWhoseTest(oneTerm);
      if (specificationList == nil) {
         if ((searchSpec == nil) && (logicalOperator != kAENot))
            searchSpec = oneSpecification;
         else {
            specificationList = new TSearchSpecList;
            if (searchSpec)
            searchSpec = nil;
      else {
         if (oneSpecification != nil)
   if (specificationList != nil)
      searchSpec =
         new TLogicalSpec(logicalOperator, specificationList);
   if (searchSpec == nil)
   return searchSpec;
ParseComparisonOperator (Listing 6) first tests to make sure that the comparison operator is of the correct format. (Again, the code in this listing recognizes only a specific flavor of comparison operator; see the code on the CD for a more complete example.) If the operator passes that test, a new search specification representing the comparison is created and returned.

Listing 6. Parsing comparison descriptors

      ParseComparisonOperator(DescType comparisonOperator,
      TDescriptor& object1, TDescriptor& object2)
   TAbstractSearchSpec*   searchSpec = nil;
   TDescriptor          desiredClassDesc, containerDesc, 
                        keyFormDesc, keyData;
   if ((object1.DescriptorType() != typeObjectSpecifier) ||
        (object2.DescriptorType() == typeObjectSpecifier))
   desiredClassDesc = object1.GetDescriptor(keyAEDesiredClass);
   containerDesc = object1.GetDescriptor(keyAEContainer);
   keyFormDesc = object1.GetDescriptor(keyAEKeyForm);
   keyData = object1.GetDescriptor(keyAEKeyData);
   if (containerDesc.DescriptorType() != typeObjectBeingExamined)
   if (keyFormDesc.GetEnumeration() == formPropertyID)
      searchSpec = new TGenericSearchSpec(keyData.GetDescType(),
                           comparisonOperator, object2);
   return searchSpec;


The code presented up to this point is the easy part: implementing the marking and whose callbacks, parsing whose descriptors, and creating search specifications can all be done with a small amount of isolated code. Doing a search on a set of elements or performing a complex operation on a collection of tokens is a bit more involved, though, and requires a well-integrated framework that supports these concepts uniformly. You're in luck -- the sample application included on this issue's CD has such a framework.

The sample application is called Scriptable Database. As its name implies, it's a database that's fully scriptable; in fact, it's usable only through AppleScript -- it has no user interface whatsoever. It's no coincidence that the model the database uses follows AppleScript's element containment model very closely. The Scriptable Database has documents that can be created, saved, and opened. Documents contain elements; elements have properties and data and may contain more elements. The database itself is completely generic; it doesn't care what the classes of the elements are or what properties they contain. To use it for a specific application, you'll have to edit Scriptable Database's dictionary, also called its AppleScript terminology extension ('aete' resource), to add the terms you'll need for your database.

    AppleScript terminology extensions are described in Inside Macintosh: Interapplication Communication, pages 7-15 to 7-20 and Chapter 8.*
All the techniques described in this article are implemented in the source code of the Scriptable Database application -- in particular, the application supports marking, and it resolves whose clauses itself (very quickly, I might add). It's an object-oriented application written in C++ based on a set of reusable foundation class libraries that you might find useful as a starting point in your own scriptable application. The source code is divided into the following subprojects:
  • The Database subproject contains a standalone C++ object database. The code in this project is not discussed in this article, but you might find it interesting to peruse.

  • The Base subproject contains pure C++ code that has no dependencies on any Mac OS or Toolbox routines, or any code from any other subproject in Scriptable Database.

  • The Blue subproject contains C++ wrapper classes for Macintosh managers used by Scriptable Database.

  • The Foundation subproject contains the foundation classes that Scriptable Database uses to implement scripting, and as such is the focal point of this article.

  • The Scripting subproject contains the object accessors and event handlers needed to respond to the messages sent by AppleScript and the OSL.

  • The Application subproject contains all the code that defines the Scriptable Database application; in fact, all the code specific to Scriptable Database is in this subproject. Every other subproject is also used in some other application that I've worked on.
Note that these subprojects are layered such that each one uses code found only within that subproject or in a more primitive subproject. The Database subproject is used only by the Scripting and Application subprojects; all other subprojects are used freely by any subproject listed below it. The foundation classes will be discussed in depth in this article; comprehension of the rest of the sample code is left as an exercise for the reader. (You didn't think I could write an entire article and not say that at least once, did you?)


The focal point of the foundation classes is the class TAbstractScriptableObject. This class was designed to serve as a base class, but it may also be mixed into an existing class hierarchy with multiple inheritance, as was done in the sample application (see the class TScriptableDocument). Any object derived from TAbstractScriptableObject can be used as a token for the OSL. Memory management of tokens must be done carefully; note that in most instances, tokens passed to the OSL are temporary and must be deleted when the token disposal callback is called. In other instances, though, it may be more convenient to use an existing object that the application has already created -- for example, a document object.

Because of this, the token disposal callback must be able to unambiguously determine the difference between the temporary objects and those objects it should not delete, or disaster will result. Designators -- objects that represent some portion of another object -- are used for the temporary objects. The class TAbstractScriptableObject defines the methods CloneDesignator and DisposeDesignator, which do nothing in the abstract case. Designators override these methods to copy and dispose of themselves -- sometimes in conjunction with a reference-counting scheme.

As you might expect, the methods of TAbstractScriptableObject are designed to provide functionality that closely matches the features of the OSL. All objects derived from this class have elements and properties and can be sent events generated from an Apple event that the application receives. There are virtual methods in TAbstractScriptableObject that you can override to provide each of these types of behavior in your objects.


A scriptable object exports its elements by providing an iterator object that knows how to iterate over the appropriate set of objects. There are two methods that return iterators, ElementIterator and DirectObjectIterator.
virtual TAbstractScriptableObject* ParentObject(); 
virtual TAbstractObjectIterator* ElementIterator();
virtual TAbstractObjectIterator* DirectObjectIterator();
The ParentObject method returns the object that this object is an element of. The element iterator iterates over the elements of the object, as was previously mentioned; the direct object iterator usually returns an iterator that knows about a single object -- the TAbstractScriptableObject that created it. If the object is actually a collection, however, its direct object iterator will iterate over every element in the collection. Once your application provides an iterator for the elements of its objects, the code in the foundation classes can handle most of the standard access methods for you. The access methods supported include formAbsolutePosition and formName, the default ordinals (all, first, last, and so on), and ranges of items (for example, items 1 through 10).

Your application's scriptable classes can support more specialized access methods by overriding the appropriate method:

virtual TAbstractScriptableObject* Access(DescType desiredClass,
      DescType keyForm, TDescriptor keyData);
virtual TAbstractScriptableObject*
      AccessByUniqueID(DescType desiredClass, TDescriptor uniqueID);
virtual TAbstractScriptableObject*
      AccessByOrdinal(DescType desiredClass, DescType ordinal);
The first method, Access, is the general object-accessor dispatch method that calls the more specific access method appropriate for the keyForm parameter. You can override this method to define custom access forms -- for example, the Scriptable Finder defined the forms formCreator (to access an application by its creator type) and formAlias (to access a file or folder through an alias record). The method AccessByUniqueID provides a mapping from a unique ID to an object; override this method if your objects have unique IDs that scripts can use to access them. The method AccessByOrdinal handles ordinal access. All ordinals defined in the Apple Event Registry are supported by the implementation in the base class, so your application will probably never need to override AccessByOrdinal.


Every scriptable object has at least a few properties that it must support. Almost all classes will have these five properties:
  • pName, since most objects have names

  • pClass, pBestType, and pDefaultType, since the Apple Event Registry requires that all objects support these properties

  • pContents, since the foundation classes handle Get Data and Set Data events by using this property
To advertise the existence of a property, your scriptable classes can override the methods BestType, DefaultType, and CanReturnDataOfType; these methods are used by the Get Data event handler to determine what data type it should ask for when it requests the property data from the object through GetProperty.
virtual DescType BestType(DescType propertyName);
virtual DescType DefaultType(DescType propertyName);
virtual Boolean CanReturnDataOfType(DescType propertyName, 
      DescType desiredType);
However, your application doesn't have to override these methods to provide information about every property of an object, since it's also possible (and more convenient) to describe the properties of an object in a property description table. For example, the properties defined in TAbstractScriptableObject are shown in the following property description table:
   TAbstractScriptableObject::fPropertiesOfClass[] = {
     { pName,        kReserved,  typeChar,        typeChar },
     { pClass,       kReserved,  typeType,        typeType },
     { pDefaultType, kReserved,  typeType,        typeType },
     { pBestType,    kReserved,  typeType,        typeType },
     { pID,          kReserved,  typeLongInteger, typeLongInteger },
     { pIndex,       kReserved,  typeLongInteger, typeLongInteger }
Each entry in this table consists of four long words: the property identifier, a long word reserved for use by the class that defines the property, the property's best type, and the property's default type. The property description table is referenced through the class data table, so properties defined in one class are automatically inherited by any class that derives from it. The methods BestType and DefaultType return information from the property description table if an entry for the requested property can be found, and the method CanReturnDataOfType returns true if the desired type is the best type or the default type for a property.
    See the files Object.cp and Object.h in the sample code for information on the class data tables. The macros DeclareMinClassData and ImplementMinClassData are used for classes that have no class properties; classes that do have class properties use the macros DeclareClassData and ImplementClassData.*
The reserved long word from the property description table is always passed to the GetProperty and SetProperty methods; it can be used to provide information to assist in obtaining the data for the requested property.

virtual TDescriptor GetProperty(DescType propertyName,
      DescType desiredType, unsigned long additionalInfo);
virtual void SetProperty(TTransaction* transaction,
      DescType propertyName, TDescriptor& data,
      unsigned long additionalInfo);
The reserved long word can have nearly any value, but should not be greater than or equal to the constant kReservedRangeForPropertyInfo (see AbstractScriptableObject.h).

In addition to making the application's properties easier to implement, the property description table is key in supporting the "properties" property (which returns the current value of all the properties of an object, as specified by the property description table). It's also very useful for accessing properties of collections of tokens, as described later.

The transaction parameter in the SetProperty method must be provided by the caller but is not used by the foundation classes. It's provided as a mechanism whereby transaction-based applications (such as Scriptable Database) can make all changes under the auspices of a transaction object. Once all changes are made successfully, the transaction changes are committed back into the database. If anything goes wrong, the transaction is aborted and all changes are backed out. To the foundation class, TTransaction is just a named object that has no methods. The event handlers in the Scripting subproject use code from the Database subproject to create a transaction to pass to SetProperty (and other methods that can change the contents of the database), and commit or back out of the changes as appropriate after the event completes successfully or fails.

In some rare cases, it may be undesirable to include a property in the property description table, or it may be inconvenient to implement all of the functionality of a property strictly through the GetProperty and SetProperty methods. For example, Scriptable Finder has a trash property that returns a reference to the Trash object on the desktop. In such cases, your application should override the method AccessByProperty to return an appropriate scriptable object that represents the property:

virtual TAbstractScriptableObject* AccessByProperty(DescType 
The object returned by AccessByProperty can be any sort of scriptable object; unlike properties described solely by the property description table, it can have properties above and beyond the minimum (for example, pClass, pBestType, and pDefaultType), and it can receive events (such as Empty Trash). Properties that are returned through AccessByProperty can also appear in the property description table, but if they do, the reserved long word should contain the magic constant kNeverCreateGenericProperty.


Most scriptable applications use one of two dispatch techniques for handling Apple events: event-first or object-first dispatching. In event-first dispatching, an event is first dispatched to an event handler, which resolves the direct parameter and passes it a message appropriate to the Apple event being received. The advantage of event-first dispatching is that the parameters of the event are well known and can be extracted and passed to the object from the event handler, reducing the amount of duplicate code scattered through the various object event handlers. The disadvantage is that event-first dispatching requires a large number of very similar event handlers, and the message-passing API is often large (one method per event).

Object-first dispatching attempts to solve this problem by providing a single event handler that blindly resolves the direct parameter of the received Apple event and passes the event to the resulting object. This technique is much simpler than event-first dispatching, requires a smaller API, and usually does exactly the right thing. But object-first dispatching doesn't always do exactly the right thing. For example, an Apple event that copies a set of objects to some destination container would send a different Copy event to every item in the source; what you might prefer is to have a single Copy event sent to the destination object, with the list of items to copy included as a parameter to the event. You'd never get the latter with object-first dispatching.

The Scriptable Database application uses a combination of event-first and object-first dispatching. Most Apple events are processed by a common event handler that resolves the direct parameter and passes the message along, in object-first dispatching style. Certain special events, however, such as Move, Copy, and Create Element, are processed in their own event handler, which can send a message to some object other than the direct parameter of the Apple event. The two primary methods that events are sent to are AECommand and CreateNewElement.

AECommand is defined as follows:

virtual TDescriptor AECommand(TTransaction* transaction, TAEvent ae, 
      TAEvent reply, long aeCommandID, TAbstractScriptableObject*
      auxObjects = nil, long auxInfo = 0);
Both the Apple event message and the reply are passed to the event handler, just in case they need to be accessed directly. The AECommand method should not put the command result into the reply directly, though, as it might not be the only object that's receiving this message. Instead, it should return the result as the return value of the method, and allow the event handler to collect all the results into a descriptor list and package them in the reply.

The meaning of the parameters auxObjects and auxInfo depends on the event handler that's processing the message; the aeCommandID parameter implicitly defines what the AECommand method should expect to find in these parameters. For example, in the Move and Copy events, the auxObjects parameter contains the set of objects that should be moved or copied. Providing a single method with general-purpose, multiple-definition parameters allows different scriptable applications that use the same foundation classes to define new events that have custom parameters without requiring them to change or expand the API of the foundation classes. This is one of the advantages of object-first dispatching that we definitely want to keep in our design.

The Create Element event is special enough to warrant giving it its own dispatch message:

virtual TAbstractScriptableObject* CreateNewElement(TTransaction* 
    transaction, TAEvent ae, TAEvent reply, DescType newObjectClass,
    TDescriptor initialData, TDescriptor initialProperties, Boolean& 
    usedInitialData, Boolean& usedInitialProperties);
In most cases, classes that override CreateNewElement only need to look at the newObjectClass parameter, create a new object of that class, and return a reference to the newly created object. The event handler calls the SetData method of the new object by using the with data parameter from the Create event, and then calls the SetProperty method of the new object with each of the properties specified in the with properties parameter from the Create event. The initial data and initial properties for the new element are also provided as parameters to CreateNewElement in case they're needed at create time. If the usedInitialData or usedInitialProperties parameter is set to true, the event handler is inhibited from calling SetData or SetProperty, respectively, on the new object.


As previously mentioned, objects derived from TAbstractScriptableObject can be grouped into collections of tokens that can be passed around as a single object. The class that implements most of this functionality is TProxyToken, which is publicly derived from TAbstractDesignator. (A collection object is a temporary object created only to manage the collection of tokens and must be disposed of when the collection is no longer needed; therefore, a proxy must be a designator.) There are a number of different types of collections, each derived from the class TProxyToken.The classes of proxies provided in the foundation classes include the following:
  • TEveryItemProxy -- every element of an object

  • TEntireContents -- every item in the entire deep hierarchy

  • TMarkToken -- a collection of tokens accumulated from the marking callbacks or from resolving a whose clause
Other types of collections are also possible. For example, the selection token is a proxy for the set of items that are currently selected, so the token for the selection would also derive from TProxyToken (however, since the Scriptable Database has no user interface, it has no selection object).

Sending a message to a proxy token usually does nothing more than pass the message on to each of its delegates; for example, the open selection script would pass an Open event to every selected item. In other cases, however, the proxy token handles the event itself. For instance, set selection to item 1 doesn't send a Set Data event to the selected items; instead, it deselects the currently selected items and selects the items in the direct parameter (such as item 1 in the previous example). The exact behavior of the proxy is determined by the concrete class (for example, TEveryItemProxy) that derives from the abstract class TProxyToken, but the proxy token does provide some mechanisms that can be used by its descendants to control the meaning of certain messages.

Properties in particular are handled in a special way by proxies. Some properties will apply to the proxy object itself, whereas other properties will refer to the delegates of the proxy token. For example, the script default type of selection should return the default data type for the selection object (which would be of type typeAEList), whereas default type of every item whose name contains "e" should return a list of default types, one for each item that matches the query every item whose name contains "e". There is no heuristic that can be used to determine which properties should apply to the proxy and which should apply to the proxy's delegates; the only solution is to list all the properties that should be sent to the proxy object in some way. In the foundation classes, this is done with the method PropertyAppliesToProxy:

Boolean PropertyAppliesToProxy(DescType propertyName);
Each class that derives from TProxyToken should override PropertyAppliesToProxy and return true for those properties that should be processed by the proxy object and false for those that should be sent to the proxy's delegates.


Previous sections of this article described how a whose clause was received by the object accessors of an application, converted into a search specification, and then resolved with a simple element iterator. Now that you're familiar with the capabilities of the foundation classes, we can go into the workings of the search specifications in a little more detail.

As you may recall, there are two types of search specification: logical and comparative. The primary operation of a search specification is to take a token and return whether or not that item is a member of the set specified by the comparator. A logical specification contains a list of other specifications; it does nothing more than call the comparator method of each, and either logically AND or logically OR the results together. A comparative search specification needs to perform some test on a property of an object that was passed to it; it does so by calling the CompareProperty method of the object being tested.

virtual Boolean CompareProperty(DescType propertyIdentifier,
      DescType comparisonOperator, TDescriptor compareWith);
The property identifier, the comparison operator, and the literal data to compare with were all extracted from the whose descriptor, as described previously. The default implementation of CompareProperty calls the object's GetProperty method and compares the result with the literal data by using the specified comparison operator. (You'll find a routine that compares two descriptors in the file MoreAEM in the Blue subproject of the sample application.) Note, however, that calling GetProperty involves a memory allocation to create a descriptor for holding the property data. Memory allocations are something best avoided in the inner loop of an operation that's supposed to progress quickly, so the performance of a whose clause resolution can be improved if you override CompareProperty and do common property comparisons without a memory allocation.


Using the techniques described up to this point, your application can resolve whose clauses, and do so much faster than the OSL would. However, there are other optimizations that you can make to further improve performance. The techniques described so far perform better than the OSL for two primary reasons:
  • They limit the number of memory allocations needed, as much as possible.

  • They reduce the number of callbacks that need to be made between the OSL and your application. This is particularly important if your application is PowerPC native but uses the emulated 680x0 OSL.
Also, note that if your implementation of access by index is O(N) rather than constant time, the OSL's whose clause resolution will be O(N2), since it will have to call your O(N) access by index callback N times. Even if you ignore this article completely and don't resolve whose clauses yourself, you should as an absolute minimum cache the last token returned by your formAbsolutePosition accessor and ensure that the next call to the accessor can be done in constant time if the container token and desired class are the same and the index is 1 greater. This will speed up your whose clause resolution considerably.

However, even for all of the performance gains that these techniques provide, whose clauses are still resolved according to the same basic algorithm used by the OSL. As anyone who has dabbled in computer information-science theory knows, it's often more advantageous to switch algorithms completely and put off fine-tuning until after the correct algorithm has been found.

Unfortunately, it's not possible to do any better than what we've already done in the general case (a direct linear search of the search space, comparing every item to the search specification in order). Doing a binary search isn't possible unless your search space happens to be sorted by your search key -- not very likely, and in any event it's impossible to know whether it is or not unless you have specific knowledge about the search space. Searching the entire contents of a deep hierarchy -- such as all the folders on a disk -- is one type of search space that can often be optimized.

In cases where the search space is well known, it's often possible to abandon the idea of direct iteration and use some other algorithm to search. For example, if you're writing code to search the entire contents of a disk, you would be much better off calling PBCatSearch, which walks through the entries in the catalog record in the order they happen to appear on the disk, ignoring the disk's hierarchy. This technique is so much faster than doing a deep traversal of the disk's catalog that doing a deep search of some subfolder on a disk is usually much better accomplished by searching the entire disk and weeding out the matches that aren't somewhere inside the search's root container. In cases where you have access to a search engine with characteristics similar to PBCatSearch, you should go out of your way to try to use it. Of course, this may well require yet another conversion of the search specification, but the performance gains will outweigh the initial cost. The foundation classes presented in this article have hooks that allow the incorporation of existing search engines to be incorporated into the process of resolving whose clauses.

When a whose clause is being resolved, the task of doing the search is delegated to the iterator object returned by the root of the search. Putting the method in the iterator rather than in the object allows different types of iterators to provide different search algorithms, each optimized to its own search space. The iterator returned by the TEntireContents proxy has a special implementation of AccessBySearchSpec; instead of using the implementation it inherits from TAbstractIterator, it uses a method called SearchDeep in the element iterator of the root object. The default implementation of SearchDeep does nothing more than compare every item in the deep hierarchy below each of its elements, and add those that match to the collection. This is really no different from what would happen if TEntireContents::AccessBySearchSpec just called through to Inherited::AccessBySearchSpec, but it does provide a hook enabling special iterators to insert their own search engines if they have a technique that will do deep searches faster than a straightforward deep iteration.

Listing 7 shows the default implementation of SearchDeep; note that it does a deep search on each of the elements of the iterator rather than simply a single deep search. The reason for this is that iterators aren't required to have a single root object that one could conceivably search deep from; once you have an iterator, the only knowledge at your disposal is the set of objects that the iterator "contains." The information as to where the iterator came from isn't available to every iterator, although some (such as TDeepIterator) do save a reference to it.

Listing 7. Doing a deep search

TAbstractScriptableObject* TDeepIterator::AccessBySearchSpec(
      DescType desiredClass, TAbstractSearchSpec* searchSpec)
   TObjectCollector   collector;

   TAbstractObjectIterator* iter = fRootItem->ElementIterator();
   iter->SearchDeep(&collector, desiredClass, searchSpec);
   return collector.CollectionResult();

void TAbstractObjectIterator::SearchDeep(TAbstractCollector*
      collector, DescType desiredClass,
      TAbstractSearchSpec* searchSpec)
   TDeepIterator deepIter(nil);
   for (this->Reset(); this->More(); this->Next()) {
      TAbstractScriptableObject* elementToDeepSearch =
      for (deepIter.Reset(); deepIter.More();
           deepIter.Next()) {
         TAbstractScriptableObject* token = deepIter.Current();
         if (token->DerivedFromOSLClass(desiredClass) &&
         token = nil; 
      if (elementToDeepSearch->DerivedFromOSLClass(desiredClass) && 
In Listing 7, rather than having the deep search iterator create and return a collection of tokens, a collector object is passed in and given the responsibility of making a collection from the results of the search, which it's passed one item at a time. This is done so that other parts of your scriptable application can call SearchDeep to do deep searches if they need to, and providing a collector object allows this code the flexibility to process the search results one item at a time, as they are found, rather than waiting for the entire search to complete.

Note the following line in Listing 7:

A search engine that's hooked into this code path might, in a multithreaded application, execute asynchronously under its own thread. In these instances, the search engine needs a way to tell the collector that it's still running, and might call collector->AddToCollection with more search results at any time. The search engine does this by attaching a dynamic behavior object to the collection that understands the kWaitForAsyncSearchesToComplete message (see "What Is a Dynamic Behavior?"). When this message is received, the search engine's collector behavior must block the current thread of execution until the search engine completes its search.


    A dynamic behavior is an object that can be attached to some other object to change its behavior dynamically at run time. Only objects that are specially written to accept behaviors can have behaviors attached to them, and

    only certain methods of that object can be dynamically changed by the behavior object.

    Methods that support dynamic behaviors contain additional code that first dispatches to any behavior attached to the object and then does the default action for that method. But the actual flow of control is somewhat different from that.

    Suppose you have an abstract class TObject that supports behaviors, and an abstract class TBehavior that provides an interface for an object that can dynamically change the behavior of any TObject-derived object. If TObject has a method called Command that the behavior could modify, the implementation of TObject::Command would look like this:

       TBehavior*   behavior;
       behavior = this->FirstBehavior();
       if (behavior)
       TBehavior*   behavior;
       behavior = this->NextBehavior();
       if (behavior)
    Given this definition for the Command method, some class derived from TBehavior could override the virtual method TBehavior::CommandDynamicBehavior, and call Inherited to execute the default action of the method it's overriding. This allows behaviors to do both pre- and post-processing. The cost to supporting behaviors is additional dispatch time, but the advantage is the powerful, dynamic extensibility of your objects.
The use of a collector object and a dynamic behavior object allows the searching code to be flexible, optimized independently of other search engines, and reusable, even to other code that might not have exactly the same needs as the scripting code.

Also note the implementation of the functions TEveryItem::SearchDeep and TMarkToken::SearchDeep. Both of these call the function RecursiveSearchDeep, which calls SearchDeep on each of the elements of the iterator in turn. Without this special code path, a script such as (entire contents of every disk) whose name contains "mac" would end up using the slow deep-iteration search, and miss out on the optimized SearchDeep method of each disk. Calling the SearchDeep method of each disk independently enables different types of disks to have different types of search engines; for example, searches of remote disks might be optimized differently than searches of local disks, and not every type of volume supports PBCatSearch. In a framework that has provisions for optimizations, flexibility of design is extremely important.


It doesn't take too much work to vastly improve the performance of your scriptable application, and the techniques presented in this article will help you do just that. Resolving whose clauses yourself can speed up the execution of your event processing by a factor of ten to a hundred; a chance to gain that level of improvement is hard to ignore.

AppleScript is one of the most compelling technologies that Apple offers -- the ability to record scripts, modify them, and play them back later puts powerful automation into the hands of programming novices. However, AppleScript is only as cool as the scriptable applications available in the marketplace. If you've written a scriptable application, thank you. If you haven't yet taken the OSL plunge, by all means read some of the material referred to in this article and dive in. (You might also want to take a look at the "According to Script" column that follows this article.) In either case, you should find the sample code on this issue's CD to be a very useful aid in implementing fast and complete scripting support in your Macintosh application.


    • Inside Macintosh: Interapplication Communication (Addison-Wesley, 1993).
    • "Apple Event Objects and You" by Richard Clark, develop Issue 10, and "Better Apple Event Coding Through Objects" by Eric M. Berdahl, develop Issue 12. These articles provide good descriptions of the OSL.

    Greg Anderson is enjoying the hot days of late summer as he writes this, but by the time this issue is in your hands, he should be back on the ski slopes earning his nickname, "Air Bear." Greg spends most of his skiing time looking for some protrusion to jump or fall off of while wearing his favorite polar bear hat. He sometimes works, too; he recently moved to Japan to work on international software for Apple Technologies in Tokyo.

    Thanks to our technical reviewers Dan Clifford, Eric House, Arnoldo Miranda, and Jon Pugh.


Community Search:
MacTech Search:

Software Updates via MacUpdate

jAlbum 19.2 - Create custom photo galler...
With jAlbum, you can create gorgeous custom photo galleries for the Web without writing a line of code! Beginner-friendly, with pro results - Simply drag and drop photos into groups, choose a design... Read more
BlueStacks 4.140.13 - Run Android applic...
BlueStacks App Player lets you run your Android apps fast and fullscreen on your Mac. Feature comparison chart Version 4.140.13: Highlights/Bug Fixes: Feel free to use BlueStacks as your go to... Read more
Adobe Premiere Pro CC 2020 14.0.1 - Digi...
Premiere Pro CC 2020 is available as part of Adobe Creative Cloud for as little as $52.99/month. The price on display is a price for annual by-monthly plan for Adobe Premiere Pro only Adobe Premiere... Read more
VirtualBox 6.1.2 - x86 virtualization so...
VirtualBox is a family of powerful x86 virtualization products for enterprise as well as home use. Not only is VirtualBox an extremely feature rich, high performance product for enterprise customers... Read more
RoboForm 8.6.8 - Password manager; syncs...
RoboForm is a password manager that offers one-click login, mobile syncing, easy form filling, and reliable security. Password Manager. RoboForm remembers your passwords so you don't have to! Just... Read more
Postbox 7.0.11 - Powerful and flexible e...
Postbox is a new email application that helps you organize your work life and get stuff done. It has all the elegance and simplicity of Apple Mail, but with more power and flexibility to manage even... Read more
calibre 4.9.0 - Complete e-book library...
Calibre is a complete e-book library manager. Organize your collection, convert your books to multiple formats, and sync with all of your devices. Let Calibre be your multi-tasking digital librarian... Read more
Notability 4.2 - Note-taking and annotat...
Notability is a powerful note-taker to annotate documents, sketch ideas, record lectures, take notes and more. It combines, typing, handwriting, audio recording, and photos so you can create notes... Read more
FoldersSynchronizer 5.0.1 - Synchronize...
FoldersSynchronizer is a popular and useful utility that synchronizes and backs-up files, folders, disks and boot disks. On each session you can apply special options like Timers, Multiple Folders,... Read more
Sketch 62 - Design app for UX/UI for iOS...
Sketch is an innovative and fresh look at vector drawing. Its intentionally minimalist design is based upon a drawing space of unlimited size and layers, free of palettes, panels, menus, windows, and... Read more

Latest Forum Discussions

See All

The Alliance update to Out There: Omega...
Out There is an old go-to recommendation for a lot of mobile stalwarts, but I could never really get into it. This sci-fi survival game that blended elements of interactive fiction and roguelike mechanics just felt a little off-balance and a little... | Read more »
Animal Fury Destination is an action-adv...
Animal Fury Destination is an action-adventure game from independent, Colombian developer Ignicion Games. It's a 3D action game where you'll play as various different characters as you embark on a quest to stop an evil crow sorcerer. [Read more] | Read more »
Shadowgun War Games Closed Beta Impressi...
Shadowgun: War Games is an upcoming free-to-play multiplayer shooter that’s essentially just an Overwatch knock-off. There are hero characters with special abilities, and you compete in 5-v-5 game modes where the goal is to use superior team... | Read more »
Slingsters is a physics-based puzzler fo...
Slingsters is a physics-based puzzle game where the aim is to collect various different monsters by flinging them from one side of a level to the other and into a box. It's also the first game from Nappy Cat and is available now for iOS and... | Read more »
Spiritwish's latest update sees the...
A sizeable update has hit Nexon's MMORPG Spiritwish today. It brings a new game mode, characters and there will also be a special event to celebrate the update with a firework display. [Read more] | Read more »
Maze Machina, a turn-based puzzler from...
The latest game from Arnold Rauers also known as Tiny Touch Tales is now available. You may be familiar with one of his many excellent titles such as Card Crawl, Enyo and Card Thief. His latest endeavour is called Maze Machina and you can grab it... | Read more »
Mario Kart Tour's Ice Tour races to...
Can you believe Mario Kart Tour is already on its 9th tour? The game only launched back in September, and since then it's become increasingly tricky to keep on top of the amount of new content Nintendo is pumping out. [Read more] | Read more »
Apple Arcade: Ranked - Top 50 [Updated 1...
In case you missed it, I am on a quest to rank every Apple Arcade game there is. [Read more] | Read more »
Marvel Future Fight's latest update...
Marvel Future Fight's latest update has added an all-new team of heroes to recruit and do battle with. The 'Warriors of the Sky' include Blue Dragon, War Tiger, Sun Bird, and Shadow Shell. As is the norm, each character comes with their own unique... | Read more »
Klee: Spacetime Cleaners is a fast-paced...
Klee: Spacetime Cleaners is a fast-paced auto-shooter that sports a cute retro aesthetic thathad racked up an impressive 100,000 pre-registers prior to its release. It's available now for both iOS and Android. [Read more] | Read more »

Price Scanner via

Just in! Apple iMacs on sale for $100-$150 of...
B&H Photo has new 2019 21″ and 27″ 5K iMacs on stock today and on sale for up to $150 off Apple’s MSRP, with prices starting at only $999. These are the same iMacs sold by Apple in their retail... Read more
Save $100 on the 13″ 1.4GHz MacBook Pro at th...
Apple resellers have 13″ 1.4GHz MacBook Pros on sale today for $100 off Apple’s MSRP, and some are including free overnight delivery: (1) Amazon has new 2019 13″ 1.4GHz MacBook Pros on sale for $100... Read more
AT&T offers free 64GB Apple iPhone XS wit...
Open a new line of service with AT&T, and they will include a free 64GB iPhone XS. Credit for the phone is applied monthly over a 30 month lease. The fine print: “Limited Time Requires new line... Read more
New Verizon deal: Apple iPhone XR for $300 of...
Switch to Verizon and sign up with one of their Unlimited plans, and Verizon will take $300 off the price of an Apple iPhone XR (regularly $749), plus get a free $200 prepaid Mastercard. This is an... Read more
Amazon’s popular AirPods sale is back with mo...
Amazon has new 2019 Apple AirPods on sale today ranging up to $40 off MSRP, starting at $129, as part of their popular Apple AirPods sale. Shipping is free: – AirPods Pro: $234.98 $15 off MSRP –... Read more
Apple’s top of the line 10.5″ 256GB WiFi + Ce...
B&H Photo has the top of the line 10.5″ 256GB WiFi + Cellular iPad Air on sale for $599 shipped. That’s $180 off Apple’s MSRP for this model and the cheapest price available. Overnight shipping... Read more
Apple’s refurbished iPad Pros are the cheapes...
Apple has Certified Refurbished 11″ iPad Pros available on their online store for up to $220 off the cost of new models. Prices start at $679. Each iPad comes with a standard Apple one-year warranty... Read more
Just in: Take $100 off the price of the 3.0GH...
Apple resellers are offering new 2018 6-Core Mac minis for $100 off Apple’s MSRP today, only $999. B&H Photo has 6-Core Mac minis on sale for $100 off Apple’s standard MSRP. Overnight shipping is... Read more
Apple has 4-core and 6-core 2018 Mac minis av...
Apple has Certified Refurbished 2018 Mac minis available on their online store for $120-$170 off the cost of new models. Each mini comes with a new outer case plus a standard Apple one-year warranty... Read more
Amazon offers $200 discount on 13″ MacBook Ai...
Amazon has new 2019 13″ MacBook Airs with 256GB SSDs on sale for $200 off Apple’s MSRP, now only $1099, each including free shipping. Be sure to select Amazon as the seller during checkout, rather... Read more

Jobs Board

*Apple* Mobility Pro - Best Buy (United Stat...
**744429BR** **Job Title:** Apple Mobility Pro **Job Category:** Store Associates **Store NUmber or Department:** 000574-Garner-Store **Job Description:** At Best Read more
Geek Squad *Apple* Consultation Professiona...
**757963BR** **Job Title:** Geek Squad Apple Consultation Professional **Job Category:** Store Associates **Store NUmber or Department:** 000433-Henrietta-Store Read more
*Apple* Computing Professional - Best Buy (U...
**754611BR** **Job Title:** Apple Computing Professional **Job Category:** Store Associates **Store NUmber or Department:** 000142-Milpitas-Store **Job Read more
Best Buy *Apple* Computing Master - Best Bu...
**745058BR** **Job Title:** Best Buy Apple Computing Master **Job Category:** Store Associates **Store NUmber or Department:** 001080-Lake Charles-Store **Job Read more
Geek Squad *Apple* Consultation Professiona...
**756640BR** **Job Title:** Geek Squad Apple Consultation Professional **Job Category:** Store Associates **Store NUmber or Department:** 000484-Manchester-Store Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.