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.

 
AAPL
$97.19
Apple Inc.
+2.47
MSFT
$44.87
Microsoft Corpora
+0.04
GOOG
$595.98
Google Inc.
+1.24

MacTech Search:
Community Search:

Software Updates via MacUpdate

Firefox 31.0 - Fast, safe Web browser. (...
Firefox for Mac offers a fast, safe Web browsing experience. Browse quickly, securely, and effortlessly. With its industry-leading features, Firefox is the choice of Web development professionals... Read more
Little Snitch 3.3.3 - Alerts you to outg...
Little Snitch gives you control over your private outgoing data. Track background activityAs soon as your computer connects to the Internet, applications often have permission to send any... Read more
Thunderbird 31.0 - Email client from Moz...
As of July 2012, Thunderbird has transitioned to a new governance model, with new features being developed by the broader free software and open source community, and security fixes and improvements... Read more
Together 3.2 - Store and organize all of...
Together helps you organize your Mac, giving you the ability to store, edit and preview your files in a single clean, uncluttered interface. Smart storage. With simple drag-and-drop functionality,... Read more
Cyberduck 4.5 - FTP and SFTP browser. (F...
Cyberduck is a robust FTP/FTP-TLS/SFTP browser for the Mac whose lack of visual clutter and cleverly intuitive features make it easy to use. Support for external editors and system technologies such... Read more
iExplorer 3.4 - View and transfer all th...
iExplorer is an iPhone browser for Mac lets you view the files on your iOS device. By using a drag and drop interface, you can quickly copy files and folders between your Mac and your iPhone or... Read more
Airmail 1.4 - Powerful, minimal email cl...
Airmail is a powerful, minimal mail client.It was designed to retain the same experience with a single or multiple accounts and provide a quick, modern and easy-to-use user experience. Airmail... Read more
Macs Fan Control 1.1.12 - Monitor and co...
Macs Fan Control allows you to monitor and control almost any aspect of your computer's fans, with support for controlling fan speed, temperature sensors pane, menu-bar icon, and autostart with... Read more
A Better Finder Rename 9.37 - File, phot...
A Better Finder Rename is the most complete renaming solution available on the market today. That's why, since 1996, tens of thousands of hobbyists, professionals and businesses depend on A Better... Read more
MacBook Air EFI Firmware Update 2.9 - Fo...
MacBook Air EFI Firmware Update is recommended for MacBook Air (Mid 2011) models. This update addresses an issue where systems may take longer to wake from sleep than expected and fixes a rare issue... Read more

Latest Forum Discussions

See All

Together for iOS (Productivity)
Together for iOS 1.0 Device: iOS Universal Category: Productivity Price: $9.99, Version: 1.0 (iTunes) Description: Together is an app for keeping things in one place. Notes, documents, images, movies, sounds, web pages and bookmarks... | Read more »
The Phantom PI Mission Apparition (Game...
The Phantom PI Mission Apparition 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: ** Release sale! 50% off for a limited time! ** The Phantom PI Mission Apparition is a spooky, puzzly, rock’... | Read more »
The Great Prank War (Games)
The Great Prank War 1.0.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0.0 (iTunes) Description: Help Mordecai, Rigby, Muscle Man and Skips take the park back from Gene and his goons with a plethora of prank-related... | Read more »
Teenage Mutant Ninja Turtles (Games)
Teenage Mutant Ninja Turtles 1.0.0 Device: iOS Universal Category: Games Price: $3.99, Version: 1.0.0 (iTunes) Description: Download the all new Teenage Mutant Ninja Turtles Official Movie Game! | Read more »
Dream Revenant (Games)
Dream Revenant 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: EXCLUSIVE LAUNCH PRICE ! Dream Revenant is at $1.99 for a limited time ! | Read more »
Traps n' Gemstones (Games)
Traps n' Gemstones 1.00 Device: iOS Universal Category: Games Price: $2.99, Version: 1.00 (iTunes) Description: LAUNCH SALE! 40% off, JULY ONLY! TRAPS N' GEMSTONES is an adventurous platform game, among gamers typically known as the... | Read more »
Soccer Physics (Games)
Soccer Physics 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: One-button soccer game! So dumb it's fun. "Soccer Physics is probably the funniest football game you'll play on iOS" —... | Read more »
Ex-Angry Birds Developers Release Monsu...
Ex-Angry Birds Developers Release Monsu Teaser Trailer Posted by Jennifer Allen on July 23rd, 2014 [ permalink ] Finnish developer Boomlagoon has released a teaser trailer of their forthcoming side-scrolling action platformer, | Read more »
Dragons: Rise of Berk – Tips, Tricks, an...
Things have changed in Berk, the fantasy Viking village of DreamWorks’ How to Train Your Dragon series. Dragons and Vikings, once mortal enemies, now must learn to live together in peace. Dragons: Rise of Berk lets players manage dragon-Viking... | Read more »
Cowabunga! Teenage Mutant Ninja Turtles:...
Cowabunga! Teenage Mutant Ninja Turtles: Rooftop Run Is Currently Free Posted by Jennifer Allen on July 23rd, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »

Price Scanner via MacPrices.net

What Should Apple’s Next MacBook Priority Be;...
Stabley Times’ Phil Moore says that after expanding its iMac lineup with a new low end model, Apple’s next Mac hardware decision will be how it wants to approach expanding its MacBook lineup as well... Read more
ArtRage For iPhone Painting App Free During C...
ArtRage for iPhone is currently being offered for free (regularly $1.99) during Comic-Con San Diego #SDCC, July 24-27, in celebration of the upcoming ArtRage 4.5 and other 64-bit versions of the... Read more
With The Apple/IBM Alliance, Is The iPad Now...
Almost since the iPad was rolled out in 2010, and especially after Apple made a 128 GB storage configuration available in 2012, there’s been debate over whether the iPad is a serious tool for... Read more
MacBook Airs on sale starting at $799, free s...
B&H Photo has the new 2014 MacBook Airs on sale for up to $100 off MSRP for a limited time. Shipping is free, and B&H charges NY sales tax only. They also include free copies of Parallels... Read more
Apple 27″ Thunderbolt Display (refurbished) a...
The Apple Store has Apple Certified Refurbished 27″ Thunderbolt Displays available for $799 including free shipping. That’s $200 off the cost of new models. Read more
WaterField Designs Unveils Cycling Ride Pouch...
High end computer case and bag maker WaterField Designs of San Francisco now enters the cycling market with the introduction of the Cycling Ride Pouch – an upscale toolkit with a scratch-free iPhone... Read more
Kingston Digital Ships Large Capacity Near 1T...
Kingston Digital, Inc., the Flash memory affiliate of Kingston Technology Company, Inc.,has announced its latest addition to the SSDNow V300 series, the V310. The Kingston SSDNow V310 solid-state... Read more
Apple’s Fiscal Third Quarter Results; Record...
Apple has announced financial results for its fiscal 2014 third quarter ended June 28, 2014, racking up quarterly revenue of $37.4 billion and quarterly net profit of $7.7 billion, or $1.28 per... Read more
15-inch 2.0GHz MacBook Pro Retina on sale for...
B&H Photo has the 15″ 2.0GHz Retina MacBook Pro on sale for $1829 including free shipping plus NY sales tax only. Their price is $170 off MSRP. B&H will also include free copies of Parallels... Read more
Apple restocks refurbished Mac minis for up t...
The Apple Store has restocked Apple Certified Refurbished Mac minis for up to $150 off the cost of new models. Apple’s one-year warranty is included with each mini, and shipping is free: - 2.5GHz Mac... Read more

Jobs Board

Sr Software Lead Engineer, *Apple* Online S...
Sr Software Lead Engineer, Apple Online Store Publishing Systems Keywords: Company: Apple Job Code: E3PCAK8MgYYkw Location (City or ZIP): Santa Clara Status: Full Read more
Senior Interaction Designer, *Apple* Online...
**Job Summary** Apple is looking for a hands on Senior…will be a key player in designing for the Apple Online Store. The ideal designer will have a Read more
*Apple* Sales Chat Rep - Apple (United State...
…is looking for motivated, outgoing, and tech savvy individuals who want to offer Apple Customers an unparalleled customer experience over chat. At Apple , we believe Read more
Mac Expert - *Apple* Online Store Mexico -...
…MUST be fluent in English and Spanish to be considered for this position At Apple , we believe that hard work, a fun environment, creativity and innovation fuel the Read more
*Apple* Industrial Design CAD Sculptor - App...
**Job Summary** The Apple Industrial Design team is looking for a CAD sculptor/Digital 3D modeler to create high quality CAD models used in the industrial design process Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.