TweetFollow Us on Twitter

HIObject: The Carbon Object Model

Volume Number: 18 (2002)
Issue Number: 10
Column Tag: Carbon Development

HIObject: The Carbon Object Model

Learn about the new Toolbox object model introduced in Mac OS X 10.2

by Ed Voas

Introduction

Apple's Human Interface Toolbox (HIToolbox) has always been object-oriented. There were various objects, such as windows, controls, menus, etc. and those objects could be manipulated by APIs meant to deal with them (ShowWindow, et. al.). But there was no really good way to override standard controls or even derive a new custom control from another control. HIObject is a new mechanism that allows you to subclass and override standard toolbox objects as well as treat those objects in a polymorphic way. This article explains all you need to know about this new world, and serves as a foundation for learning about the other HIToolbox technologies that are in Jaguar.

What Is HIObject?

HIObject is Apple's new common object base class in Jaguar for the HIToolbox. It is the foundation for everything Apple does these days in Carbon -- it is something that all Carbon developers should learn about and understand.

People long assumed Apple had a C++ hierarchy under the hood for things like controls, but in reality they never did. The move to HIObject was something that came out of the move to a real C++ framework internally for basic object types. Under the skin, the Jaguar Toolbox is a wildly different beast than in previous releases. Most of this change comes from Apple's re-architecting to use HIObject and HIView (the new view system in Jaguar).

Essentially, an HIObject is any type of object that can send and receive Carbon Events -- windows, menus, controls, etc. -- basically all the objects that had an event target anyway. HIObject is an object model where an HIObject is the object-oriented encapsulation of an event target, and the 'methods' you call to manipulate this object are implemented as Carbon Events.

To be exact, windows, views/controls, menus, the application object, toolbars, and toolbar items all derive from HIObject. Diagram 1 shows a portion of the current HIObject hierarchy. The purpose of doing this is to gain some form of polymorphic behavior when dealing with these objects. For example, if you obtain a reference to a WindowRef, you can safely call HIObject routines on it, such as HIObjectGetEventTarget.


Diagram 1: The HIObject Hierarchy

Having Toolbox objects all derive from a single-base class might not seem important at first glance, but as you start to use HIObject more and more, you will realize that the Toolbox has just taken a huge step forward into an exciting new world. The more Apple improves the Toolbox, the faster they can make changes and add features. This change has done incredible amounts of good already -- the addition of the Accessibility features in Jaguar required much less effort due to the new implementation.

The new data type Apple has introduced to represent an HIObject is an HIObjectRef. The new objects introduced in the Jaguar Toolbox, such as toolbars, are merely typedef'd to HIObjectRef. Legacy types such as ControlRef are not typedef'd to maintain source code compatibility. For example, if Apple typedef'd ControlRef and WindowRef both to HIObjectRef, and you had an overloaded C++ method that took a ControlRef in one variant and a WindowRef in another, your code would probably no longer compile since they equated to the same type. Rather than wreak havoc, Apple decided to keep things the way they were. You can simply cast references to those types into HIObjectRefs as needed.

The new HIObject.h header file contains a routine to create an HIObject, but nothing to retain or release the object. Well then how are you supposed to use an object you could never destroy? Well, in reality Apple has done something cool -- HIObjects are actually Core Foundation types! That means you can add anything that is derived from HIObject (even a window) into a CF collection, such as a CFArray. It also means you simply use CFRetain and CFRelease to retain/release the object. Wicked! But you do need to be careful about such things, as windows for example may not go away when you expect them to if you are retaining them in places. You can also cause circular retention (which sounds very painful), so be careful.

Polymorphic Functions

Let's first check out the routines you can call on any HIObject. There aren't that many at present. That's a good thing, as it makes it easier to learn.

Event Handling

To get the event target of any HIObject, you merely call HIObjectGetEventTarget. This is very nice! It means you can now keep an array of dissimilar objects such as controls and windows, and just iterate the list, getting their event targets. For example, you can use this technique to keep a list of objects that are interested in receiving particular notifications. It doesn't matter if it's a window, control, or toolbar item. All you care about is that they accept carbon events. There is no need to track what type of object they are and call the appropriate API to get the target.

Class Identity

An HIObject class is uniquely identified by its class ID, which is a CFString. Apple uses Java-style namespacing for its classes (com.apple.blah). To obtain the class ID of an object, you call HIObjectCopyClassID. This returns a copy of the class ID string for you to inspect. You can compare it to other class IDs to see if an object is of a particular type, for example. Be warned that if a class ID is not documented in any header (and at present only two of them are), you should not rely on those class IDs remaining constant between releases of Mac OS.

It's also useful to find out if an object you have is something of a specific type -- typically referred to as an 'is-a' test. For example, if you have a push button's ControlRef in hand, you can see if it's a control by asking if it is derived from HIView:

if ( HIObjectIsOfClass( anObject, kHIViewClassID ) )
{
   // It's an HIView!
}
else
{
   abort();
}

Believe it or not, we're running out of polymorphic functions! As I mentioned before, you can use CF routines to retain or release an HIObject. So the following is perfectly legal now:

CreatePushButtonControl( window, ..., &control );
// do some fun stuff here, maybe add it to an array,
// which will retain it. Removing it from the array
// would decrease the retain count as expected. You
// must use the standard CF type callbacks when you
// create the array though.
CFRelease( control );

CFRelease is now a synonym for DisposeControl, DisposeWindow, and DisposeMenu. New types (like the toolbar) have no specific retain or release calls of their own.

Debugging

In the Toolbox, there are several functions you can call to print debugging info for an object to stdout. Some of them aren't exported, so you can only call them from gdb. And the names are not consistent -- even engineers at Apple can seldom remember what they are. This was fixed in HIObject by introducing one base class function to display the debugging information for an object -- HIObjectPrintDebugInfo. Call it with a window or control reference and you will see all the types of information it prints to stdout.

And much much more!

Well, not really. There are a couple of routines to deal with Accessibility, another major feature in Jaguar. Accessibility is well outside the scope of this article though, and deserves an entire article of its own.

There are also a couple of other APIs you should know. These are important primarily when creating objects of your own design. That's precisely what we'll cover next, so we'll talk about them as we go.

Creating Your Own Objects

You can create HIObject classes of your own and even subclass ones provided by the Toolbox. You would most likely do this to create a custom view or toolbar item. Or you might create a custom HIObject so you can have your own event target to pass to Toolbox APIs.

Let's get right into how to subclass something. We will create a simple subclass of HIObject. The first thing that we need to do is register our new subclass with the HIObject system. You do this via a call to HIObjectRegisterSubclass:

extern OSStatus 
HIObjectRegisterSubclass(
   CFStringRef                     inClassID,
   CFStringRef                     inBaseClassID,
   OptionBits                     inOptions,
   EventHandlerUPP               inConstructProc,
   UInt32                           inNumEvents,
   const EventTypeSpec *      inEventList,
   void *                           inConstructData,
   HIObjectClassRef *         outClassRef );
For our purposes, we would call this function as follows:
const EventTypeSpec kMyFunObjectEvents =
{   { kEventClassHIObject, kHIObjectConstruct },
   { kEventClassHIObject, kHIObjectDestruct }
};
#define kMyFunObjectID \
   CFSTR( "com.mycompany.funobject" )
HIObjectRegisterSubclass(
   kMyFunObjectID,
   NULL,    // no base class
   0,       // no options
   MyClassHandler,
   GetEventTypeCount( kMyFunObjectEvents ),
   kMyFunObjectEvents,
   0,       // no handler data
   &classRef );

By passing NULL for the inBaseClass parameter, we are telling the function that we want to be a subclass of HIObject itself. This function sets up MyClassHandler as a handler that will automatically get pushed onto an instance of this class when one is created. In this example, this handler only deals with two events -- construct, and destruct. These two events are, in fact, required for any subclass you are registering. If you try to register a class handler that does not respond to these events, the earth will open up and swallow you. Either that, or an error will be returned.

Figure 1 shows our class handler for our custom object.

Listing 1: Class event handler

// simple object 
struct MyFunObject {
   HIObjectRef   ref;
   Boolean         isFunEnabled;
};
OSStatus MyClassHandler(
   EventHandlerCallRef    inCallRef,
   EventRef                   inEvent,
   void *                      inUserData )
{
      OSStatus            result = eventNotHandledErr;
      UInt32               theClass, theKind;
      MyFunObject*      object = (MyFunObject*)inUserData;
      // Please note that object above is overloaded in this handler.
      // for the kEventHIObjectConstruct event ONLY, the inUserData
      // parameter is the user data you passed to HIObjectRegisterSubclass.
      // For all other calls to this function, it is the object pointer that you create
      // and return in your handling of the kEventHIObjectConstruct as seen below.
      theClass = GetEventClass( inEvent );
      theKind = GetEventKind( inEvent );
      switch(    theClass )
      {
         case kEventClassHIObject:
            switch ( theKind )
            {
               case kEventHIObjectConstruct:   
                  {
                     HIObjectRef      ref;
                     // When the construct event is called, you are handed the
                      // HIObjectRef that is being constructed. In this example,
                     // we save it off in our object. This is what you generally
                     // want to do so you can call appropriate Toolbox APIs
                     // as needed, since your object pointer (created below),
                     // is not a real HIObject.
                  result = GetEventParameter( inEvent,
                                        kEventParamHIObjectInstance,
                                       typeHIObjectRef, 
                                       NULL,
                                       sizeof( HIObjectRef ),
                                       NULL,
                                       &ref );
                     require_noerr( result, ParameterMissing );
                     // Create the fun object here. If we fail to malloc it, return
                     // an error. (If you ever wondered why sometimes the Toolbox
                     // returns memFullErr on a system where you can never run
                     // out of memory without the system dying a horrible death,
                     // now you know!) We are reusing the object variable above
                     // since it's not used for anything in this particular example
                     // during construction.
                     object = malloc( sizeof( MyFunObject ) );
                     require_action( object, CantAllocObject,
                                          result = memFullErr );
                     object->ref = ref;
                     object->isFunEnabled = false;
                     // OK. Here's the key: we replace the instance parameter
                     // with our object pointer. The type of the parameter MUST
                     // be typeVoidPtr. It's the Law. The Toolbox will store this
                     // off with the HIObject. This will allow you to call
                     // HIObjectDynamicCast later if you need to to get your
                     // object pointer back from an HIObjectRef.
                     SetEventParameter( inEvent,
                                       kEventParamHIObjectInstance,
                                typeVoidPtr,
                                       sizeof( void * ),
                                       &object );
                  }
                  break;
               case kEventHIObjectDestruct:
                  // This is easy. Just dispose of the object. Do NOT call through
                  // with CallNextEventHandler -- Very Bad Things will happen.
                  // This is a top down destruction. Don't try to get fancy!
                  free( object );
                  break;
            }
            break;
      }
CantAllocObject:
ParameterMissing:
      return result;
}

How an HIObject is constructed

In order to truly understand what is going on in the class handler, let's discuss the steps the Toolbox takes when creating an HIObject.

The Toolbox sends construction events bottom-up, as you would expect in C++ or similar runtime models. This means that base classes are constructed before subclasses.

First, the Toolbox creates the base HIObject. This is where the actual HIObjectRef value is created. It is important to note that unlike C++ (where the subclass' this pointer is the same value as the base class' this pointer), a subclass' object pointer is not technically an HIObject. It is merely data stored with the HIObject for the specific class. We'll see how this works later. After the base HIObject is created, all other subclasses of HIObject between it and your class are constructed. This means that if you are creating a object of type Foo, which derives from Bar, which derives from HIObject, first the HIObject is created, then Bar, and finally comes your 15 minutes of stardom.

Once the Toolbox creates your immediate superclass, it starts the process of constructing your part of this aggregate HIObject. First, it takes the event handler you registered and installs it onto the event target that was created for the HIObject. As mentioned, this handler must be registered for the kEventHIObjectConstruct and kEventHIObjectDestruct events.

Next, the Toolbox directly calls your handler with a kEventHIObjectConstruct event. When called directly, you are not being called in the context of a handler stack, so you cannot call CallNextEventHandler, unless you like to crash. The inUserData parameter of your class handler is passed the value you specified for the inConstructData parameter when you registered the class. Typically, during construction you will allocate memory for your own instance data. This allocation might be as simple as calling malloc or NewPtr, or it might involve creating your own C++ object. In the construct event, you are passed the base HIObjectRef of the object being created. You should store this HIObjectRef in your own instance data for later use. You should then use SetEventParameter to set the kEventParamHIObjectInstance parameter in the construction event with your instance data. You must use typeVoidPtr as the type.

Once back from sending the event, the Toolbox looks for your instance of typeVoidPtr in the event and stores it with the object. It also sets the user data parameter of the event handler it installed to be this instance data. Following the construct event, all calls to your event handler will have the instance data you returned to the Toolbox. At this point, all events are now sent to your object using standard Carbon Event mechanisms. It is only the construct event that is special.

Once construction has completed successfully, the Toolbox will send your object a kEventHIObjectInitialize event. The initialization stage is optional; i.e. an object does not need to respond to the initialize event unless it is expecting certain parameters to be passed to it at creation time. This is where those parameters may be fetched. We'll show an example of this shortly. The first thing you should do is call through to the 'inherited' method with CallNextEventHandler. Once back from that, you should verify the result code returned is noErr, indicating that the base class initialized properly. If it did, you should extract any initialization parameters and do whatever your object requires in order to properly initialize. If the base class did not initialize properly, you should return the error that CallNextEventHandler returned as the result of your handler immediately, doing no work. The Toolbox will see the error code and proceed to destroy the object (see 'Object Destruction,' below). Your object must be able to be destroyed in a partially initialized state such as this.

Upon successful initialization, the HIObjectRef is returned to the caller of HIObjectCreate. From there, you can have all sorts of cool fun.

Object Destruction

Destruction is top down, as in C++. When an object's retain count reaches zero, the object is destroyed. During destruction, the Toolbox sends a kEventHIObjectDestruct event to the object. This event will just propagate using the normal rules of event handlers (top-down), which is exactly what we want. It is a very bad thing to call CallNextEventHandler during destruction. Just clean up and return from your handler.

Creating an instance of your class

To create an instance of this new object class, all we need to do is make this call:

HIObjectRef      obj;
err = HIObjectCreate(
   kMyFunObjectID,
   NULL, // no initialize event
   &obj );

At this point, you have a nice object of your own design. But it doesn't do much, does it? You can create it and release it. Let's add an API to set the fun enabled boolean. See listing 2 for the code.

Listing 2: Adding APIs to your class

OSStatus MyFunObjectSetFunEnabled(
   HIObjectRef    inObject,
   Boolean         inEnabled )
{
   MyFunObject*      obj;
   OSStatus            err = noErr;
   // Cast the HIObjectRef handed to us to our internal instance data.
   // What this does is look up the data we returned in the
   // kEventParamHIObjectInstance parameter when we handled the
   // kEventHIObjectConstruct event in listing 1. If this function
   // returns NULL, then the object is not of the class specified in
   // the second parameter.
   obj = (MyFunObject*)HIObjectDynamicCast(
               inObject, kMyFunObjectID );
   require_action( obj, InvalidObject,
                        err = kMyInvalidClassID );
   // OK. We have our object now. Store the value
   obj->isFunEnabled = inEnabled;
InvalidObject:
   return err;
}

This is pretty straightforward, but there is one oddity -- the dynamic cast call. It's necessary because the API we wrote takes an HIObjectRef and not a MyFunObject pointer. This starts getting into the ugly truth of it all -- your objects are not really HIObjects! When it comes right down to it, they are just some value that you wish to associate with an HIObject.

Diagram 1 shows a comparison between a C++ object layout and an HIObject layout. In C++ the object is a unified block of memory. It can do this because it's part of the language runtime. With HIObjects, the object reference always points to the HIObject, and the data stored by subclasses are kept track of in the HIObject itself.

Remember that when you handle the kEventHIObjectConstruct event, you are given the HIObjectRef for your object ahead of time. The object we created in listing 1 is just our blob of data that we want stored with the HIObject for our class. We get that data back with HIObjectDynamicCast if we happen to have an HIObjectRef in hand.

If you think about it though, this 'casting' mimicks C++ very well, in that you can't take a pointer to a base class in a class method without 'casting up' to your class before using it. So while the guts are different from C++, the mentality is not. The main difference is that we can't take our struct pointer and treat it exactly like an HIObject, because, like, it's not.

One last thing to note is that while HIObject's data layout is somewhat disjoint, the 'vtable' layout is just as you'd expect. There is one unified Event Target that acts as the vtable. Only the data is spread out. Also, there's nothing to say that in the future Apple couldn't come up with some superior scheme to try to make it better. There is an 'options' parameter in the call to HIObjectRegisterSubclass, so it would be possible to define new types of subclasses if Apple so desired.


Diagram 1: C++ vs. HIObject Data Layout

At this point, you know all the hard stuff. The rest is just academic. Let's extend our object a bit. First, let's write a new API to create an object. It will wrap the call to HIObjectCreate. It will also take a parameter indicating the initial value of our 'fun enabled' boolean.

Listing 3: Our Creation API

OSStatus MyFunObjectCreate(
   Boolean             inFunEnabled,
   HIObjectRef*    outObject )
{
   EventRef         event;
   OSStatus         err = noErr;
   HIObjectRef   object;
   // To pass parameters to our object at creation time, we need to use a
   // Carbon Event. We create it here and add our boolean parameter. We
   // then pass it into HIObjectCreate. This event will be sent to our
   // object at initialize time. You must take care to use the correct class
   // and ID for this event.
   err = CreateEvent( NULL, kEventClassHIObject,
               kEventHIObjectInitialize, GetCurrentEventTime(),
               0, &event );
   require_noErr( err, CantCreateInitEvent );
   SetEventParameter( event, kMyFunEnabledParam,
                typeBoolean, sizeof( Boolean ), &inFunEnabled );
   err = HIObjectCreate( kMyFunObjectID, event, outObject );
   ReleaseEvent( event );
CantCreateInitEvent:
   return err;
}

To pass initial parameters to an object, you need to create a Carbon Event with the right class and kind. You simply add your parameters onto that event and pass it into HIObjectCreate. The sort of code you see in listing 3 is exactly the sort of stuff Apple does in the Toolbox. Calls such as CreatePushButtonControl are implemented using this exact same formula.

Now, in our class handler, we need to add a case to handle the initialize event. See listing 4 for how to do that.

Listing 4: Adding the Initialize Handler

OSStatus MyClassHandler(
      EventHandlerCallRef    inCallRef,
      EventRef                   inEvent,
      void *                      inUserData )
{
      OSStatus            result = eventNotHandledErr;
      MyFunObject*      object = (MyFunObject*)inUserData;
         .
         .
         .      
               case kEventHIObjectInitialize:
                  // Be sure to call our 'inherited' initialize first. Then extract
                  // our parameter and leave.
                  
                  result = CallNextEventHandler( inCallRef,
                                 inEvent );
                  if ( result == noErr )
                  {
                     result = GetEventParameter( inEvent, 
                           kMyFunEnabledParam,
                            typeBoolean, NULL,
                           sizeof( Boolean ), NULL,
                           &object->isFunEnabled );
                  }
                  break;
         .
         .
         .
}

Well, that was easy. Of course, your initialize handler might be much more complicated. As seen in the code, you should call through before handling the initialize event yourself. There are some exceptions to this, but it depends on how your object is set up and whether initializing the base class will cause one of your event handlers to be called before you are ready. But you should treat calling through first as the rule.

Now, for completeness, let's add support for a couple of other things. First, let's add support for equality testing. If someone has two references to two of your object instances, they could call CFEqual on them to see if they are the same. CFEqual calls into the HIObject implementation, and that in turn sends a Carbon Event to your instance. By the time the event gets to you, the Toolbox has already checked to see whether the references are identical and that the class of object is the same. So you know that you will be passed an instance of the same class as yours. All you need to do is compare your internal state and return the result in the event. Listing 5 shows the handler code to do this.

Listing 5: Adding the Equality Handler

OSStatus MyClassHandler(
      EventHandlerCallRef    inCallRef,
      EventRef                   inEvent,
      void *                      inUserData )
{
      OSStatus            result = eventNotHandledErr;
      MyFunObject*      object = (MyFunObject*)inUserData;
         .
         .
         .      
               case kEventHIObjectIsEqual:            
                  {
                     Boolean         localResult;
                     HIObjectRef   otherHIObject;
                     MyFunObject*   other;
                     // Get the direct object. It will be the object we are being
                     // compared to.
                     err = GetEventParameter( inEvent,
                           kEventParamDirectObject,
                           typeHIObjectRef, NULL,
                           sizeof( HIObjectRef ), NULL,
                           &otherHIObject);
                     require_noErr( err, MissingParameter );
                     // Now cast it to get the data pointer for the other object.
                     // This cast should never fail since we are guaranteed to
                     // be looking at an object of the same class by the time
                     // we get here.
                     other = (MyFunObject*)HIObjectDynamicCast(
                              otherHIObject, kMyFunObjectID );
                     check( other != NULL );
                     // compare the two objects' guts. For our object types, we'll
                     // consider them equal if their isFunEnabled settings are the same.
                     localResult = ( other->isFunEnabled ==
                                             object->isFunEnabled );
 
                     // Now store the result in the kEventParamResult parameter
                     // as typeBoolean. We are done. Exit with an appropriate result.
                     SetEventParameter( inEvent,
                               kEventParamResult,
                               typeBoolean, sizeof( Boolean ),
                              &localResult );
                     result = noErr;
                  }
                  break;
         .
         .
         .
MissingParameter:
   
   return err;
}

As you can see, it's simple to handle the equality event. Just extract the direct object parameter and cast it to get the object data pointer. Then make the comparison however your class decides to and store the result in the event.

There's only one more event to handle: kEventHIObjectPrintDebugInfo. This is sent to your handler when someone calls HIObjectPrintDebugInfo. Big surprise, I'm sure. Check out listing 6 for how to handle this event.

Listing 6: Adding the Debugging Handler

OSStatus MyClassHandler(
      EventHandlerCallRef    inCallRef,
      EventRef                   inEvent,
      void *                      inUserData )
{
      OSStatus            result = eventNotHandledErr;
      MyFunObject*      object = (MyFunObject*)inUserData;
         .
         .
         .      
               case kEventHIObjectPrintDebugInfo:
                  {
                     fprintf( stdout, "MyFunObject" );
                     fprintf( stdout, "   Fun Enabled: %d",
                        object->isFunEnabled );
                     result = noErr;
                  }
                  break;
         .
         .
         .
MissingParameter:
   
   return err;
}

Wow. That was simple. All you do to respond to this is print your information to stdout. Typically, you'd print the name of the class, and then any information under that, indented a bit. The Toolbox has a standard formatter for its output. You should in general try to match the look of the Toolbox output. In the future, this formatter might be exposed for use by developers.

That's All Folks

Well, believe it or not, you now know all you need to about HIObjects. We've covered the different polymorphic functions and shown you how to create HIObject classes and objects of your own design. We've also demonstrated dynamic casting and covered every event that gets sent to your HIObject from the HIObject subsystem.

With this knowledge in hand, you can do things such as add custom toolbar items for the new HIToolbar class in Jaguar. You do this by subclassing the HIToolbarItem class using the steps shown in this article. You can also start to write custom views based on HIView. Remember, HIObject permeates everything in the Toolbox in Jaguar, so it's a good thing to understand what it is and how it works. Get out there and start creating your own HIObjects!

Special thanks to David McLeod, Eric Schlegel, Guy Fullerton, Curt Rothert, Matt Ackeret, and Bryan Prusha for reviewing this article.


Ed Voas is the Manager/Tech Lead of the Carbon High Level Toolbox. When not coding, he is usually out applying 5 coats of polish to his car.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Google Earth 7.1.8.3036 - View and contr...
Google Earth gives you a wealth of imagery and geographic information. Explore destinations like Maui and Paris, or browse content from Wikipedia, National Geographic, and more. Google Earth combines... Read more
QuickBooks 16.1.11.1556 R12 - Financial...
QuickBooks helps you manage your business easily and efficiently. Organize your finances all in one place, track money going in and out of your business, and spot areas where you can save. Built for... Read more
FileZilla 3.24.0 - Fast and reliable FTP...
FileZilla (ported from Windows) is a fast and reliable FTP client and server with lots of useful features and an intuitive interface. Version 3.24.0: New The context menu for remote file search... Read more
Bookends 12.7.8 - Reference management a...
Bookends is a full-featured bibliography/reference and information-management system for students and professionals. Bookends uses the cloud to sync reference libraries on all the Macs you use.... Read more
Duplicate Annihilator 5.8.3 - Find and d...
Duplicate Annihilator takes on the time-consuming task of comparing the images in your iPhoto library using effective algorithms to make sure that no duplicate escapes. Duplicate Annihilator detects... Read more
BusyContacts 1.1.6 - Fast, efficient con...
BusyContacts is a contact manager for OS X that makes creating, finding, and managing contacts faster and more efficient. It brings to contact management the same power, flexibility, and sharing... Read more
MarsEdit 3.7.10 - Quick and convenient b...
MarsEdit is a blog editor for OS X that makes editing your blog like writing email, with spell-checking, drafts, multiple windows, and even AppleScript support. It works with with most blog services... Read more
BusyCal 3.1.4 - Powerful calendar app wi...
BusyCal is an award-winning desktop calendar that combines personal productivity features for individuals with powerful calendar sharing capabilities for families and workgroups. Its unique features... Read more
VirtualBox 5.1.14 - x86 virtualization s...
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
Bookends 12.7.8 - Reference management a...
Bookends is a full-featured bibliography/reference and information-management system for students and professionals. Bookends uses the cloud to sync reference libraries on all the Macs you use.... Read more

Super Mario Run dashes onto Android in M...
Super Mario Run was one of the biggest mobile launches in 2016 before it was met with a lukewarm response by many. While the game itself plays a treat, it's pretty hard to swallow the steep price for the full game. With that said, Android users... | Read more »
WarFriends Beginner's Guide: How to...
Chillingo's new game, WarFriends, is finally available world wide, and so far it's a refreshing change from common mobile game trends. The game's a mix of tower defense, third person shooter, and collectible card game. There's a lot to unpack here... | Read more »
Super Gridland (Entertainment)
Super Gridland 1.0 Device: iOS Universal Category: Entertainment Price: $1.99, Version: 1.0 (iTunes) Description: Match. Build. Survive. "exquisitely tuned" - Rock Paper Shotgun No in-app purches, and no ads! | Read more »
Red's Kingdom (Games)
Red's Kingdom 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: Mad King Mac has kidnapped your father and stolen your golden nut! Solve puzzles and battle goons as you explore and battle your... | Read more »
Turbo League Guide: How to tame the cont...
| Read more »
Fire Emblem: Heroes coming to Google Pla...
Nintendo gave us our first look at Fire Emblem: Heroes, the upcoming mobile Fire Emblem game the company hinted at last year. Revealed at the Fire Emblem Direct event held today, the game will condense the series' tactical RPG combat into bite-... | Read more »
ReSlice (Music)
ReSlice 1.0 Device: iOS Universal Category: Music Price: $9.99, Version: 1.0 (iTunes) Description: Audio Slice Machine Slice your audio samples with ReSlice and create flexible musical atoms which can be triggered by MIDI notes or... | Read more »
Stickman Surfer rides in with the tide t...
Stickson is back and this time he's taken up yet another extreme sport - surfing. Stickman Surfer is out this Thursday on both iOS and Android, so if you've been following the other Stickman adventures, you might be interested in picking this one... | Read more »
Z-Exemplar (Games)
Z-Exemplar 1.4 Device: iOS Universal Category: Games Price: $3.99, Version: 1.4 (iTunes) Description: | Read more »
5 dastardly difficult roguelikes like th...
Edmund McMillen's popular roguelike creation The Binding of Isaac: Rebirth has finally crawled onto mobile devices. It's a grotesque dual-stick shooter that tosses you into an endless, procedurally generated basement as you, the pitiable Isaac,... | Read more »

Price Scanner via MacPrices.net

Twelve South Releases RelaxedLeather Cases fo...
Inspired by the laid-back luxury of burnished leather boots and crafted in rich tones of taupe, herb and marsala, RelaxedLeather cases deliver smart, easy protection for the iPhone 7. Each genuine... Read more
Week’s Best Deal: New 2016 13-inch 2.0GHz Mac...
Amazon has the new 2016 13″ 2.0GHz non-Touch Bar MacBook Pros on sale for a limited time for $225 off MSRP including free shipping: - 13″ 2.0GHz MacBook Pro, Space Gray (MLL42LL/A): $1274.99 $225 off... Read more
Back in stock: Apple refurbished Mac minis fr...
Apple has Certified Refurbished Mac minis available starting at $419. Apple’s one-year warranty is included with each mini, and shipping is free: - 1.4GHz Mac mini: $419 $80 off MSRP - 2.6GHz Mac... Read more
Apple Ranked ‘Most Intimate Brand’
The top ranked ‘”intimate” brands continued to outperform the S&P and Fortune 500 indices in revenue and profit over the past 10 years, according to MBLM’s Brand Intimacy 2017 Report, the largest... Read more
B-Eng introduces SSD Health Check for Mac OS
Fehraltorf, Switzerland based independant Swiss company- B-Eng has announced the release and immediate availability of SSD Health Check 1.0, the company’s new hard drive utility for Mac OS X. As the... Read more
Apple’s Education discount saves up to $300 o...
Purchase a new Mac or iPad using Apple’s Education Store and take up to $300 off MSRP. All teachers, students, and staff of any educational institution qualify for the discount. Shipping is free: -... Read more
4-core 3.7GHz Mac Pro on sale for $2290, save...
Guitar Center has the 3.7GHz 4-core Mac Pro (MD253LL/A) on sale for $2289.97 including free shipping or free local store pickup (if available). Their price is a $710 savings over standard MSRP for... Read more
128GB Apple iPad Air 2, refurbished, availabl...
Apple has Certified Refurbished 128GB iPad Air 2s WiFis available for $419 including free shipping. That’s an $80 savings over standard MSRP for this model. A standard Apple one-year warranty is... Read more
13-inch 2.7GHz Retina MacBook Pro on sale for...
B&H Photo has the 2015 13″ 2.7GHz/128GB Retina Apple MacBook Pro on sale for $100 off MSRP. Shipping is free, and B&H charges NY tax only: - 13″ 2.7GHz/128GB Retina MacBook Pro (MF839LL/A): $... Read more
Laptop Market – Flight To Quality? – The ‘Boo...
Preliminary quarterly PC shipments data released by Gartner Inc. last week reveal an interesting disparity between sales performance of major name PC vendors as opposed to that of less well-known... Read more

Jobs Board

*Apple* Retail - Multiple Positions - Apple,...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
*Apple* Technician - nfrastructure (United S...
Let’s Work Together Apple Technician This position is based in Portland, ME Life at nfrastructure At nfrastructure, we understand that our success results from our Read more
*Apple* Mobile Master - Best Buy (United Sta...
**467692BR** **Job Title:** Apple Mobile Master **Location Number:** 000602-Columbia MO-Store **Job Description:** **What does a Best Buy Apple Mobile Master Read more
*Apple* MAC Infrastructure Engineer - InnoCo...
Summary: Responsible for all aspects of Apple Desktop hardware. This includes research, design, test, and deploy technologies being researched by the desktop Read more
*Apple* & PC Desktop Support Technician...
Apple & PC Desktop Support Technician job in Manhattan, NY Introduction: We have immediate job openings for several Desktop Support Technicians with one of our most Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.