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
$119.00
Apple Inc.
+1.40
MSFT
$47.75
Microsoft Corpora
+0.28
GOOG
$540.37
Google Inc.
-0.71

MacTech Search:
Community Search:

Software Updates via MacUpdate

Skype 7.2.0.412 - Voice-over-internet ph...
Skype allows you to talk to friends, family and co-workers across the Internet without the inconvenience of long distance telephone charges. Using peer-to-peer data transmission technology, Skype... Read more
HoudahSpot 3.9.6 - Advanced file search...
HoudahSpot is a powerful file search tool built upon MacOS X Spotlight. Spotlight unleashed Create detailed queries to locate the exact file you need Narrow down searches. Zero in on files Save... Read more
RapidWeaver 6.0.3 - Create template-base...
RapidWeaver is a next-generation Web design application to help you easily create professional-looking Web sites in minutes. No knowledge of complex code is required, RapidWeaver will take care of... Read more
iPhoto Library Manager 4.1.10 - Manage m...
iPhoto Library Manager lets you organize your photos into multiple iPhoto libraries. Separate your high school and college photos from your latest summer vacation pictures. Or keep some photo... Read more
iExplorer 3.5.1.9 - View and transfer al...
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
MacUpdate Desktop 6.0.3 - Discover and i...
MacUpdate Desktop 6 brings seamless 1-click installs and version updates to your Mac. With a free MacUpdate account and MacUpdate Desktop 6, Mac users can now install almost any Mac app on macupdate.... Read more
SteerMouse 4.2.2 - Powerful third-party...
SteerMouse is an advanced driver for USB and Bluetooth mice. It also supports Apple Mighty Mouse very well. SteerMouse can assign various functions to buttons that Apple's software does not allow,... Read more
iMazing 1.1 - Complete iOS device manage...
iMazing (was DiskAid) is the ultimate iOS device manager with capabilities far beyond what iTunes offers. With iMazing and your iOS device (iPhone, iPad, or iPod), you can: Copy music to and from... Read more
PopChar X 7.0 - Floating window shows av...
PopChar X helps you get the most out of your font collection. With its crystal-clear interface, PopChar X provides a frustration-free way to access any font's special characters. Expanded... Read more
OneNote 15.4 - Free digital notebook fro...
OneNote is your very own digital notebook. With OneNote, you can capture that flash of genius, that moment of inspiration, or that list of errands that's too important to forget. Whether you're at... Read more

Latest Forum Discussions

See All

Raby (Games)
Raby 1.0.3 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0.3 (iTunes) Description: ***WARNING - Raby runs on: iPhone 5, iPhone 5C, iPhone 5S, iPhone 6, iPhone 6 Plus, iPad Mini Retina, iPad Mini 3, iPad 4, iPad Air,... | Read more »
Oddworld: Stranger's Wrath (Games)
Oddworld: Stranger's Wrath 1.0 Device: iOS Universal Category: Games Price: $5.99, Version: 1.0 (iTunes) Description: ** PLEASE NOTE: Oddworld Stranger's Wrath requires at least an iPhone 4S, iPad 2, iPad Mini or iPod Touch 5th gen... | Read more »
Bounce On Back (Games)
Bounce On Back 1.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0 (iTunes) Description: | Read more »
Dwelp (Games)
Dwelp 1.0 Device: iOS Universal Category: Games Price: $.99, Version: 1.0 (iTunes) Description: === 50% off for a limited time, to celebrate release === Dwelp is an elegant little puzzler with a brand new game mechanic. To complete a... | Read more »
Make Way for Fat Chicken, from the Maker...
Make Way for Fat Chicken, from the Makers of Scrap Squad Posted by Jessica Fisher on November 26th, 2014 [ permalink ] Relevant Games has announced they will be releasing their reverse tower defense game, | Read more »
Tripnary Review
Tripnary Review By Jennifer Allen on November 26th, 2014 Our Rating: :: TRAVEL BUCKET LISTiPhone App - Designed for the iPhone, compatible with the iPad Want to create a travel bucket list? Tripnary is a fun way to do exactly that... | Read more »
Ossian Studios’ RPG, The Shadow Sun, is...
Ossian Studios’ RPG, The Shadow Sun, is Now Available for $4.99 Posted by Jessica Fisher on November 26th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Mmmm, Tasty – Having the Angry Birds for...
The very first Angry Birds debuted on iOS back in 2009. When you sit back and tally up the number of Angry Birds games out there and the impact they’ve had on pop culture as a whole, you just need to ask yourself: “How would the birds taste... | Read more »
Rescue Quest Review
Rescue Quest Review By Jennifer Allen on November 26th, 2014 Our Rating: :: PATH BASED MATCH-3Universal App - Designed for iPhone and iPad Guide a wizard to safety by matching gems. Rescue Quest might not be an entirely original... | Read more »
You Can Play the Final Chapter of Lone W...
You Can Play the Final Chapter of Lone Wolf: Dawn Over V’taag Right Now Posted by Jessica Fisher on November 26th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »

Price Scanner via MacPrices.net

Black Friday: $300 off 15-inch Retina MacBook...
 B&H Photo has the new 2014 15″ Retina MacBook Pros on sale for $300 off MSRP as part of their Black Friday sale. Shipping is free, and B&H charges NY sales tax only: - 15″ 2.2GHz Retina... Read more
2014 1.4GHz Mac mini on sale for $449, save $...
 B&H Photo has the new 1.4GHz Mac mini on sale for $449.99 including free shipping plus NY tax only. Their price is $50 off MSRP, and it’s the lowest price available for this new model. Adorama... Read more
Early Black Friday pricing on 27-inch 5K iMac...
 B&H Photo continues to offer Black Friday sale prices on the 27″ 3.5GHz 5K iMac, in stock today and on sale for $2299 including free shipping plus NY sales tax only. Their price is $200 off MSRP... Read more
Early Black Friday sale prices on iPad Air 2,...
 MacMall is discounting iPad Air 2s by up to $75 off MSRP as part of their Black Friday sale. Shipping is free: - 16GB iPad Air WiFi: $459 $40 off - 64GB iPad Air WiFi: $559 $40 off - 128GB iPad Air... Read more
Early Black Friday MacBook Air sale prices, $...
 MacMall has posted early Black Friday MacBook Air sale prices. Save $101 on all models for a limited time: - 11″ 1.4GHz/128GB MacBook Air: $798 - 11″ 1.4GHz/256GB MacBook Air: $998 - 13″ 1.4GHz/... Read more
Why iPhone 6 Tablet/Laptop Cannibalization Is...
247wallst.com blogger Douglas A. McIntyre noted last week that according to research posted on the Applovin blog site the iPhone 6 is outselling the iPhone 6 Plus by a wide margin . Hardly a surprise... Read more
Worldwide Tablet Growth Expected to Slow to 7...
The global tablet market is expected to record massive deceleration in 2014 with year-over-year growth slowing to 7.2%, down from 52.5% in 2013, according to a new forecast from International Data... Read more
Touchscreen Glove Company Announces New Produ...
Surrey, United Kingdom based TouchAbility specializes in design and manufacture of a wide variety of products compatible with touchscreen devices including smartphones, tablets and computers. Their... Read more
OtterBox Alpha Glass Screen Protectors for iP...
To complement the bigger, sharper displays on the latest Apple devices, OtterBox has introduced Alpha Glass screen protectors to the iPhone 6 and iPhone 6 Plus. The fortified glass screen protectors... Read more
Early Black Friday Mac Pro sale, 6-Core 3.5GH...
 B&H Photo has the 6-Core 3.5GHz Mac Pro on sale today for $3499 including free shipping plus NY sales tax. Their price is $500 off MSRP, and it’s the lowest price available for this model from... Read more

Jobs Board

*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Senior Event Manager, *Apple* Retail Market...
…This senior level position is responsible for leading and imagining the Apple Retail Team's global event strategy. Delivering an overarching brand story; in-store, Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.