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

OmniOutliner Pro 4.2 - Pro version of th...
OmniOutliner Pro is a flexible program for creating, collecting, and organizing information. Give your creativity a kick start by using an application that's actually designed to help you think. It's... Read more
VLC Media Player 2.2.1 - Popular multime...
VLC Media Player is a highly portable multimedia player for various audio and video formats (MPEG-1, MPEG-2, MPEG-4, DivX, MP3, OGG, ...) as well as DVDs, VCDs, and various streaming protocols. It... Read more
Nisus Writer Pro 2.1.1 - Multilingual wo...
Nisus Writer Pro is a powerful multilingual word processor, similar to its entry level products, but brings new features such as table of contents, indexing, bookmarks, widow and orphan control,... Read more
Tinderbox 6.2.0 - Store and organize you...
Tinderbox is a personal content management assistant. It stores your notes, ideas, and plans. It can help you organize and understand them. And Tinderbox helps you share ideas through Web journals... Read more
OmniOutliner 4.2 - Organize your ideas,...
OmniOutliner is a flexible program for creating, collecting, and organizing information. Give your creativity a kick start by using an application that's actually designed to help you think. It's... Read more
Things 2.5.4 - Elegant personal task man...
Things is a task management solution that helps to organize your tasks in an elegant and intuitive way. Things combines powerful features with simplicity through the use of tags and its intelligent... Read more
NeoOffice 2014.10 - Mac-tailored, OpenOf...
NeoOffice is a complete office suite for OS X. With NeoOffice, users can view, edit, and save OpenOffice documents, PDF files, and most Microsoft Word, Excel, and PowerPoint documents. NeoOffice 3.x... Read more
iPhoto Library Manager 4.2 - Manage mult...
iPhoto Library Manager allows you to organize your photos among multiple iPhoto libraries, rather than having to store all of your photos in one giant library. You can browse the photos in all your... Read more
Web Snapper 3.3.8 - Capture entire Web p...
Web Snapper lets you capture Web pages exactly as they appear in your browser. You can send them to a file as images or vector-based, multi-page PDFs. It captures the whole Web page - eliminating the... Read more
TeamViewer 10.0.41404 - Establish remote...
TeamViewer gives you remote control of any computer or Mac over the Internet within seconds, or can be used for online meetings. Find out why more than 200 million users trust TeamViewer! Free for... Read more

The Apple Watch Could Revolutionize the...
It’s not here yet but there’s that developing sneaky feeling that the Apple Watch, despite its price tag and low battery life, might yet change quite a lot about how we conduct our lives. While I don’t think it’s going to be an overnight... | Read more »
Mad Skills Motocross 2 Version 2.0 is He...
Mad Skills Motocross 2 fans got some good news this week as Turborilla has given the game its biggest update yet. Now you'll have access to Versus mode where you can compete against your friends in timed challenges. Turborilla has implemented a... | Read more »
Kids Can Practice Healthy Living With Gr...
Bobaka is releasing a new interactive book called Green Riding Hood  in May. The app teaches kids about yoga and organic style of life through mini-games and a fun take on the classic Little Red Riding Hood fairy tale. | Read more »
Chainsaw Warrior: Lords of the Night has...
It's time to put the Darkness back in its place now that Chainsaw Warrior: Lords of the Night has officially made it to iOS. | Read more »
A World of Ice and Fire Lets You Stalk 2...
George R. R. Martin’s A World of Ice and Fire, by Random House, is a mobile guide to the epic series. The new update gives you the Journeys map feture that will let you track the movements of 25 different characters. But don't worry, you can protect... | Read more »
Gameloft Announces Battle Odyssey, a New...
Battle Odyssey, Gameloft's newest puzzle RPG, is coming to the App Store next week. Set in the world of Pondera, you will need to control the power of the elements to defend the world from evil. You'll be able to entlist over 500 allies to aid you... | Read more »
Here's How You Can Get 5 Free Pro D...
It's only been a couple of days since Gamevil released MLB Perfect Inning 15, so building up a decent roster could still take a little time. If you'd like to speed things up a bit, then we've got a deal for you. How does five free Pro drafts sound?... | Read more »
Fusion - HDR Camera (Photography)
Fusion - HDR Camera 1.0.0 Device: iOS Universal Category: Photography Price: $1.99, Version: 1.0.0 (iTunes) Description: Fusion creates HDR (high dynamic range) photos by capturing different exposures and then combining them into one... | Read more »
Sago Mini Toolbox (Education)
Sago Mini Toolbox 1.1 Device: iOS Universal Category: Education Price: $2.99, Version: 1.1 (iTunes) Description: Come build with the Sago Mini friends! Use a wrench, try a saw, or hammer some nails. From sewing hand puppets to... | Read more »
You Should Probably Grab Hitman GO While...
Hitman GO is a surprisingly cool (yet also incredibly drastic) departure from the Hitman series. It's well worth playing for any puzzle game fans out there, and at the moment you can get your hands - or garrotte if you will - on it for a mere $0.99... | Read more »

Price Scanner via MacPrices.net

TigerText Introduces First Secure Enterprise...
TigerText, a provider of secure, real-time messaging for the enterprise, has announced the launch of TigerText for the Apple Watch. TigerText for the Apple Watch enables users to securely send and... Read more
The Conservation Fund Partners with Apple To...
The Conservation Fund has announced that it will partner with Apple to help protect working forests in the United States. The Apple initiative will conserve more than 36,000 acres of working... Read more
Clearance 13-inch 2.6GHz Retina MacBook Pro a...
B&H Photo has clearance 2014 13″ 2.6GHz/128GB Retina MacBook Pros now available for $1099, or $200 off original MSRP. Shipping is free, and B&H charges NY sales tax only. Read more
Apple refurbished 2014 13-inch Retina MacBook...
The Apple Store has Apple Certified Refurbished 2014 13″ Retina MacBook Pros available for up to $400 off original MSRP, starting at $979. An Apple one-year warranty is included with each model, and... Read more
iMacs on sale for up to $205 off MSRP, NY tax...
B&H Photo has 21″ and 27″ iMacs on sale for up to $205 off MSRP including free shipping plus NY sales tax only: - 21″ 1.4GHz iMac: $1019 $80 off - 21″ 2.7GHz iMac: $1189 $110 off - 21″ 2.9GHz... Read more
Sale! 16GB iPhone 5S for $1 with service
Best Buy is offering 16GB iPhone 5Ss for $1.00 with 2-year activation at a participating cellular provider. Choose free home shipping and activation, or buy online and activate during in-store pickup... Read more
Apple refurbished 2014 MacBook Airs available...
The Apple Store has Apple Certified Refurbished 2014 MacBook Airs available starting at $679. An Apple one-year warranty is included with each MacBook, and shipping is free. These are currently the... Read more
27-inch 3.5GHz 5K iMac on sale for $2349, sav...
 Adorama has the 27″ 3.5GHz 5K iMac in stock today and on sale for $2349 including free shipping plus NY & NJ sales tax only. Their price is $150 off MSRP. For a limited time, Adorama will... Read more
Save up to $380 on an iMac with Apple refurbi...
The Apple Store has Apple Certified Refurbished iMacs available for up to $380 off the cost of new models. Apple’s one-year warranty is standard, and shipping is free: - 27″ 3.5GHz 5K iMac – $2119 $... Read more
iFixIt Teardown Awards 12-IInch Retina MacBoo...
iFixIt has posted its illustrated teardown of the new 12-inch MacBook Retina. They note that this new MacBook is less than half the thickness of the last Apple notebook called just “MacBook” back in... Read more

Jobs Board

Sr. Technical Services Consultant, *Apple*...
**Job Summary** Apple Professional Services (APS) has an opening for a senior technical position that contributes to Apple 's efforts for strategic and transactional Read more
Sr. Project Engineer, *Apple* Education Pro...
**Job Summary** Apple Professional Services (APS) has an opening for a senior technical position that contributes to Apple 's efforts for strategic and transactional Read more
Event Director, *Apple* Retail Marketing -...
…This senior level position is responsible for leading and imagining the Apple Retail Team's global engagement strategy and team. Delivering an overarching brand 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
Communication Manager - *Apple* Pay - Apple...
**Job Summary** This position works within the Apple Pay Merchant Solutions team to create, as well as oversee the development of, materials for use by Apple Pay Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.