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
$112.94
Apple Inc.
+1.16
MSFT
$47.98
Microsoft Corpora
+0.32
GOOG
$524.87
Google Inc.
+8.52

MacTech Search:
Community Search:

Software Updates via MacUpdate

NeoOffice 2014.6 - Mac-tailored, OpenOff...
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
LibreOffice 4.3.5.2 - Free Open Source o...
LibreOffice is an office suite (word processor, spreadsheet, presentations, drawing tool) compatible with other major office suites. The Document Foundation is coordinating development and... Read more
CleanApp 5.0.0 Beta 5 - Application dein...
CleanApp is an application deinstaller and archiver.... Your hard drive gets fuller day by day, but do you know why? CleanApp 5 provides you with insights how to reclaim disk space. There are... Read more
Monolingual 1.6.2 - Remove unwanted OS X...
Monolingual is a program for removing unnecesary language resources from OS X, in order to reclaim several hundred megabytes of disk space. It requires a 64-bit capable Intel-based Mac and at least... Read more
NetShade 6.1 - Browse privately using an...
NetShade is an Internet security tool that conceals your IP address on the web. NetShade routes your Web connection through either a public anonymous proxy server, or one of NetShade's own dedicated... Read more
calibre 2.13 - Complete e-library manage...
Calibre is a complete e-book library manager. Organize your collection, convert your books to multiple formats, and sync with all of your devices. Let Calibre be your multi-tasking digital librarian... Read more
Mellel 3.3.7 - Powerful word processor w...
Mellel is the leading word processor for OS X and has been widely considered the industry standard since its inception. Mellel focuses on writers and scholars for technical writing and multilingual... Read more
ScreenFlow 5.0.1 - Create screen recordi...
Save 10% with the exclusive MacUpdate coupon code: AFMacUpdate10 Buy now! ScreenFlow is powerful, easy-to-use screencasting software for the Mac. With ScreenFlow you can record the contents of your... Read more
Simon 4.0 - Monitor changes and crashes...
Simon monitors websites and alerts you of crashes and changes. Select pages to monitor, choose your alert options, and customize your settings. Simon does the rest. Keep a watchful eye on your... Read more
BBEdit 11.0.2 - Powerful text and HTML e...
BBEdit is the leading professional HTML and text editor for the Mac. Specifically crafted in response to the needs of Web authors and software developers, this award-winning product provides a... Read more

Latest Forum Discussions

See All

This Week at 148Apps: December 15-19, 20...
Happy Holidays from 148Apps!   How do you know what apps are worth your time and money? Just look to the review team at 148Apps. We sort through the chaos and find the apps you’re looking for. The ones we love become Editor’s Choice, standing out... | Read more »
New Version of Tempo Smart Calendar Help...
New Version of Tempo Smart Calendar Helps You Plan Your Day Without Opening the App Posted by Jessica Fisher on December 22nd, 2014 [ permalink ] | Read more »
Sparkle - Advanced Cross-Synthesis (Mus...
Sparkle - Advanced Cross-Synthesis 1.0 Device: iOS Universal Category: Music Price: $6.99, Version: 1.0 (iTunes) Description: Sparkle is a tool for advanced spectral hybridizations made of several algorithms that operate on frequency... | Read more »
DICETINY is coming to iOS in 2015
DICETINY is coming to iOS in 2015 Posted by Jessica Fisher on December 22nd, 2014 [ permalink ] Fakedice has announced that they’re bringing their digital board game DICETINY to iOS. | Read more »
It Came From Canada: The Witcher Battle...
The Witcher 3: Wild Hunt may still be a few months away, but very soon players will be able to get a new taste of the acclaimed Polish RPG on their mobile devices with The Witcher Battle Arena. While it trades open-world exploration for compact... | Read more »
The Babies Get Lost Again in the New Azt...
The Babies Get Lost Again in the New Aztec Ruins Update for Light in the Dark Posted by Jessica Fisher on December 22nd, 2014 [ permalink ] | Read more »
Living Room 3D for IKEA Makes All In-App...
Living Room 3D for IKEA Makes All In-App Purchases Free Until 2015 Posted by Jessica Fisher on December 22nd, 2014 [ permalink ] | Read more »
Crossbow Warrior – The Legend of William...
Crossbow Warrior – The Legend of William Tell Review By Lee Hamlet on December 22nd, 2014 Our Rating: :: MISSES THE MARKUniversal App - Designed for iPhone and iPad Crossbow Warrior details the entertaining adventures of legendary... | Read more »
A New Update Races onto Asphalt 8: Airbo...
A New Update Races onto Asphalt 8: Airborne Posted by Jessica Fisher on December 22nd, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Workflow: Powerful Automation Made Simpl...
Workflow: Powerful Automation Made Simple Review By Campbell Bird on December 22nd, 2014 Our Rating: :: GO WITH THE FLOWUniversal App - Designed for iPhone and iPad This powerful app lets users accomplish multiple tasks at touch of... | Read more »

Price Scanner via MacPrices.net

13-inch 2.4GHz Retina MacBook Pro (Apple refu...
The Apple Store has previous-generation Apple Certified Refurbished 13″ 2.4GHz/128GB Retina MacBook Pros available for $999. Apple’s one-year warranty is standard, and shipping is free: - 13″ 2.4GHz/... Read more
Apple resellers offer free overnight shipping
The Apple Store is now offering free next-day shipping on all in stock items if ordered before 12/23/14 at 10:00am PT. Local store pickup is also available within an hour of ordering for any in stock... Read more
Holiday sales continue: MacBook Airs for up t...
 B&H Photo has 2014 MacBook Airs on sale for up to $120 off MSRP, for a limited time, for the Thanksgiving/Christmas Holiday shopping season. Shipping is free, and B&H charges NY sales tax... Read more
Holiday sale continues: 13-inch Retina MacBoo...
 B&H Photo has new 13″ MacBook Pros on sale for up to $150 off MSRP as part of their Holiday pricing. Shipping is free, and B&H charges NY sales tax only: - 13″ 2.5GHz MacBook Pro: $979 save... Read more
Holiday sale continues: 15-inch Retina MacBoo...
 B&H Photo has the new 2014 15″ Retina MacBook Pros on sale for up to $300 off MSRP for a limited time. Shipping is free, and B&H charges NY sales tax only: - 15″ 2.2GHz Retina MacBook Pro: $... Read more
Holiday sale: 13-inch 128GB MacBook Air for $...
 Best Buy has the 2014 13-inch 1.4GHz 128GB MacBook Air on sale for $849.99, or $150 off MSRP, on their online store. Choose free home shipping or free local store pickup (if available). Price valid... Read more
13-inch 2.6GHz Retina MacBook Pro on sale for...
Best Buy has lowered their price on the 2014 13″ 2.6GHz/128GB Retina MacBook Pro to $1149.99 on their online store for a limited time. That’s $150 off MSRP and the lowest price available for this... Read more
Kodak Returns to CES With New Consumer Produ...
Former photography colossus Kodak is returning to CES for the first time in three years where the Kodak booth (#21818 South Hall 1) will showcase a wide range of innovative, imaging-related products... Read more
Invaluable Launches New Eponymously -Named A...
Invaluable, the world’s largest online live auction marketplace, hhas announced the official launch of the Invaluable app for iPad, now available for download in the iTunes App Store. Invaluable... Read more
IDC Reveals Worldwide Mobile Enterprise Appli...
International Data Corporation (IDC) last week hosted the IDC FutureScape: Worldwide Mobile Enterprise Applications and Solutions 2015 Predictions Web conference. The session provided organizations... Read more

Jobs Board

*Apple* Store Leader Program (US) - Apple, I...
…Summary Learn and grow as you explore the art of leadership at the Apple Store. You'll master our retail business inside and out through training, hands-on experience, Read more
Project Manager, *Apple* Financial Services...
**Job Summary** Apple Financial Services (AFS) offers consumers, businesses and educational institutions ways to finance Apple purchases. We work with national and 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* 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* Retail - Multiple Positions (US) - A...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.