TweetFollow Us on Twitter

The Road to Code: Writing Less Code

Volume Number: 24 (2008)
Issue Number: 09
Column Tag: The Road to Code

The Road to Code: Writing Less Code

The Road to Code: Writing Less Code

by Dave Dribin

Key-Value Coding

In last month's article, I discussed the model-view-controller (MVC) design pattern and how the Cocoa framework adopts it. Figure 1 summarizes the interactions between the model, view, and controller in the MVC pattern.


Figure 1: Model-View-Controller interactions

We also looked at our simple single-window rectangle application through the MVC prism. The view portion of our application is handled by classes in the AppKit framework: NSWindow, NSTextField, NSButton, NSApplication, etc. We were able to reuse these Cocoa view objects without modifying them. The rest of the application was written using two custom classes: the Rectangle model class and a controller class called HelloWorldController. The controller class was designed using the Mediator design pattern to keep the view and model classes in sync. It responded to the button action to synchronize the model with the view.

The controller class is relatively simple code; it's basically just shuttling information back and forth between the model and view. In fact, as you start to write more applications in Mac OS X, you'll find that a lot of controller code is similar from application to application. While this does make it easier to write, as you gain more experience, it also means you spend a lot of time writing repetitively similar, boring code. This means there's more code you have to keep bug-free, which also means you're not spending your time adding new features to your application.

Introduced in Mac OS X 10.3, Cocoa bindings is a technology to help reduce and sometimes completely eliminate the amount of controller code you have to write. The idea is that Cocoa provides programmers with reusable controllers that you can you use in your application, just as the Cocoa frameworks comes with many reusable views. There's just one catch. Your models have to be written using a convention called key-value coding.

Key-Value Coding

Key-value coding, or KVC for short, affects both the authors of model classes and the users of model classes. As an author of model classes, or someone who creates model classes, you must follow certain conventions. Fortunately, we've already been following these conventions. Remember when we were first designing our Rectangle class, we used accessor methods to expose instances variables as part of good encapsulation. Thus, to expose the _width instance variable to allow the user of this class to read and write the width, we needed to have two accessor methods, a getter and a setter:

- (float)width;
- (void)setWidth:(float)width;

The getter method returns the width of a rectangle and the setter method allows someone to set the width of a rectangle. By convention, this pair of methods represents the "width" property of a rectangle. Getter methods have the same name as their property and setters methods are named set<Property>:. On Leopard, we can use the new @property syntax to simplify writing getter and setter methods like this:

@property float width;

Since we're going to be working with the Rectangle model class a bit, I'm including the interface and implementation of it in Listing 1 and Listing 2. I have removed the NSCoding support to help us focus on KVC.

Listing 1: Rectangle.h interface

#import <Foundation/Foundation.h>
@interface Rectangle : NSObject
{
    float _leftX;
    float _bottomY;
    float _width;
    float _height;
}
@property float leftX;
@property float bottomY;
@property float width;
@property float height;
@property (readonly) float area;
@property (readonly) float perimeter;
- (id)initWithLeftX:(float)leftX
           bottomY:(float)bottomY
            rightX:(float)rightX
              topY:(float)topY;
@end

Listing 2: Rectangle.m implementation

#import "Rectangle.h"
@implementation Rectangle
@synthesize leftX = _leftX;
@synthesize bottomY = _bottomY;
@synthesize width = _width;
@synthesize height = _height;
- (id)initWithLeftX:(float)leftX
           bottomY:(float)bottomY
            rightX:(float)rightX
              topY:(float)topY
{
    self = [super init];
    if (self == nil)
        return nil;
    
    _leftX = leftX;
    _bottomY = bottomY;
    _width = rightX - leftX;
    _height = topY - bottomY;
    
    return self;
}
- (float)area
{
    return _width * _height;
}
- (float)perimeter
{
    return (2*_width) + (2*_height);
}
@end

So why bother with this KVC naming convention? It not only makes reading and writing code easier, but more importantly, it also enables us to access properties dynamically by string names. All objects that inherit from NSObject have a method named valueForKey: with the following signature:

- (id)valueForKey:(NSString *)key;

They are using the word "key" here, but the key is just a property name. You'll often see "key" and "property" used interchangeably in regards to KVC. They are not always the same, but they usually are. This method allows you to retrieve a property by name. So, instead of writing code to access the width property using a method or property dot notation:

    Rectangle * rectangle = ...;
    float width = rectangle.width;
    // OR: float width = [rectangle width];
    NSLog(@"width: %.1f", width);

you could access the width property like this:

    NSNumber * width = [rectangle valueForKey:@"width"];
    NSLog(@"width: %@", width);

There are a couple of quirks here. First, because valueForKey: returns an id type, only Objective-C objects may be returned. In order to satisfy this requirement, all primitives, such as float and int, are returned as NSNumber objects. Also, the property name is passed as a string. This defeats the static checking of the compiler. If I mistype the "width" string as "wdith", the compiler does not warn me, in contrast to mistyping the width property or method name.

Properties may also be set using the setValue:forKey: KVC method:

- (void)setValue:(id)value forKey:(NSString *)key;

Again, instead of using the setter method or property dot notation:

    rectangle.width = 8;
    // OR: [rectangle setWidth:8];
you could set the width property like this:
    [rectangle setValue:[NSNumber numberWithFloat:8]
                 forKey:@"width"];

And again, we have to wrap the primitive value in an object because value is of type id and we use a string for the property name. Yes, KVC is more typing and less type-safe than using methods or properties, but it does have its advantages, as you will soon see.

Key Paths

Let's say, for instance, that we have a House class that represents its door as a Rectangle:

@interface House : NSObject { ... }
@property (readwrite, retain) Rectangle * door;
@end

Using traditional property accessor, we could get the door's width like this:

    House * house = ...;
    float width = house.door.width;
    // OR: float width = [[house door] width];

If we want to get the door's width directly using KVC, we need to use a different method, valueForKeyPath: with the signature:

- (id)valueForKeyPath:(NSString *)keyPath;

This would be used as follows:

    NSNumber * width = [house valueForKeyPath:@"door.width"];

Why a different method? Well, we are accessing two properties at once, first door and then width. Using valueForKey: assumes the key name is a single property name. A key path, on the other hand, is a list of properties separated by a dot, in a very similar fashion to the property dot notation. Thus the key path "door.width" accesses the property named "door" on the first object and then "width" on the second object. So using a key path is just shorthand for repeatedly calling valueForKey:

    NSNumber * width = [[house valueForKey:@"door"]
                        valueForKey:@"width"];

You can also set values by key path using the setValue:forKeyPath: method:

- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;

For example, to set the door's width using a key path:

    [house setValue:[NSNumber numberWithFloat:8]
         forKeyPath:@"door.width"];

So all in all, using KVC to access keys and key paths is an awkward way to access properties. I mean, why bother with all of this? If you don't know the name of the properties at compile time, then using strings to access properties is a nice alternative. But why would you not know the name of a property at compile time? The answer to this question is Cocoa bindings.

Cocoa Bindings

I briefly mentioned that Cocoa bindings allowed us to use controller classes supplied by Apple to replace much of our custom HelloWorldController class. Before going further, let's look at the code for the controller, without the use of Cocoa bindings. The interface and implementation are shown in Listing 3 and Listing 4.

Listing 3: HelloWorldController interface before bindings

#import <Cocoa/Cocoa.h>
@class Rectangle;
@interface HelloWorldController : NSObject
{
    IBOutlet NSTextField * _widthField;
    IBOutlet NSTextField * _heightField;
    IBOutlet NSTextField * _areaLabel;
    IBOutlet NSTextField * _perimeterLabel;
    
    Rectangle * _rectangle;
}
- (IBAction)calculate:(id)sender;
@end

Listing 4: HelloWorldController implementation before bindings

#import "HelloWorldController.h"
#import "Rectangle.h"
@interface HelloWorldController ()
- (void)updateAreaAndPerimeter;
@end
?@implementation HelloWorldController
- (id)init
{
    self = [super init];
    if (self == nil)
        return nil;
    
    _rectangle = [[Rectangle alloc] initWithLeftX:0
                                       bottomY:0
                                        rightX:5
                                         topY:10];
    
    return self;
}
- (void)awakeFromNib
{
    [_widthField setFloatValue:_rectangle.width];
    [_heightField setFloatValue:_rectangle.height];
    [self updateAreaAndPerimeter];
}
- (IBAction)calculate:(id)sender
{
    _rectangle.width = [_widthField floatValue];
    _rectangle.height = [_heightField floatValue];
    [self updateAreaAndPerimeter];
}
- (void)updateAreaAndPerimeter
{
    [_areaLabel setFloatValue:_rectangle.area];
    [_perimeterLabel setFloatValue:_rectangle.perimeter];
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:
    (NSApplication *)application
{
    return YES;
}
@end

Our controller currently has a number of responsibilities. It creates and holds onto a single instance of our Rectangle model class and sets up a reasonable default dimensions for the rectangle in its constructor. It is also the NSApplication delegate, and ensures the application quits if the window is closed. The rest of the code in awakeFromNib, calculate: and updateAreaAndPerimeter keep the view and the model in sync. It's these three methods that we are going to replace with Cocoa bindings.

Apple's controllers inherit from the class NSController. There are different subclasses, but the one we are interested in is called NSObjectController that is a designed for a single model object. Since our application's controller was previously dealing with a single instance of the Rectangle class, this is the kind of controller we want.

To start converting our application to use Cocoa bindings, we first need to make the rectangle instance of our controller class available to other objects. Currently, it's only an instance variable, but we want an NSObjectController to become the controller for it. Let's expose this instance variable as a property by adding a @property line to our interface:

@property (readonly) Rectangle * rectangle;

Let's also add a @synthesize line to our implementation:

@synthesize rectangle = _rectangle;

This not only exposes the rectangle instance variable, but it also creates a KVC compliant getter method. This means the this property can be accessed using valueForKey:, as above.

Our next step involves Interface Builder, so open up MainMenu.nib. It should look like Figure 2.


Figure 2: Original MainMenu.nib

The important part to notice is that we have an instance of our HelloWorldController. We're going to add an instance of NSObjectController to our nib. Type "nsobjectcontroller" into the Library window's filter field to find it, as shown in Figure 3.


Figure 3: NSObjectController in Interface Builder

Now drag this over to the nib window to create an instance of this class in our nib. Next, open up the Attributes tab of the Inspector window. You'll notice a text field called Class Name. This field is the class name of the attribute for which this is a controller. Change NSMutableDictionary to Rectangle since that is the name of our model class. Also, uncheck the Editable checkbox. The final result is shown in Figure 4.


Figure 4: NSObjectController attributes

I also suggest renaming the instance of the object controller to Rectangle Controller in the nib window, as shown in Figure 5. This will make it easier to remember the purpose of this particular controller instance.


Figure 5: Renamed object controller instance

We now need to hookup the rectangle controller to the rectangle instance of our HelloWorldController. NSObjectController has a property named content that refers to the object that it controls, so we need to set this to our rectangle instance. We could do this in code in our awakeFromNib method by creating an outlet to the object controller and calling its setContent: method, but it turns out we can set this up without any new code using Cocoa bindings.

With the Rectangle Controller still highlighted, switch to the Bindings tab of the Inspector window (it's the fourth icon from the left). Open the disclosure triangle for Content Object, and you'll be presented with a bunch of new fields. From the Bind to: popup, select Hello World Controller. In the Model Key Path field, type "rectangle" in all lower-case and hit Return. The result should look like Figure 6.


Figure 6: Rectangle controller's bindings

Congratulations! You've just setup your first Cocoa binding. We have now bound the content object to the "rectangle" key path of our HelloWorldController instance. So now you can see why accessing properties using strings is useful. Interface Builder stores the "rectangle" string, and somewhere in the bowels of the Cocoa framework, it's going to call valueForKeyPath: on our controller to get the rectangle instance, similar to this code:

    Rectangle * rectangle =
        [helloWorldController valueForKeyPath:@"rectangle"];

It uses whatever you type into the Model Key Path as the string passed to valueForKeyPath:.

Our next step is to use Cocoa bindings on the rest of the controls in this window. Select the text field to the right of the Rectangle Width label in the window, and switch to the Bindings tab of the Inspector window. Open up the disclosure triangle for Value, bind to the Rectangle Controller. Set the Model Key Path to "width." Keep the Controller Key set to "selection." Cocoa bindings is designed to handle objects getting selected and unselected from the user interface. In our case, the selection will always represent the rectangle instance we setup in the Content Object binding. The final result should look like Figure 7.


Figure 7: Rectangle width bindings

Repeat this procedure for the height, area, and perimeter text fields, binding them to the "height," "area," and "perimeter" key paths of the Rectangle Controller, respectively. Also, delete the Calculate button, as we will no longer need this. The final user interface is summarized in Figure 8.


Figure 8: Final user interface

You can now delete the awakeFromNib, calculate: and updateAreaAndPerimeter methods from HelloWorldController and you can delete all the outlets to the text fields. With bindings in place, we don't need any of this code.

If you ran the application right now, you should see the fields filled in with the initial values we setup in the constructor, namely a width, height, area, and perimeter of 5, 10, 50, and 30 respectively. However, there's one problem. If you change the width or height, the area and perimeter do not update. What's going on here?

Key-Value Observing

Before we go over the solution to this problem, we need to dig a little further into how bindings work. Look back at the MVC design pattern in Figure 1. You'll see an arrow from the Model to the Controller labeled as Notify. This means that when the model changes, it's supposed to notify the controller that something has changed. This is handled through KVC's companion technology called key-value observing, or KVO for short.

KVO allows one object to register for changes to key paths of another object. This is similar to how you can register for notifications using NSNotificationCenter. For example, say we wanted our HelloWorldController to be notified whenever the width of its rectangle instance is changed. To register for these changes, NSObject declares a method named addObserver:forKeyPath:.... We could call this in the HelloWorldController constructor as such:

- (id)init
{
    self = [super init];
    if (self == nil)
        return nil;
    
    _rectangle = [[Rectangle alloc] initWithLeftX:0
                                       bottomY:0
                                        rightX:5
                                          topY:10];
    [_rectangle addObserver:self
                 forKeyPath:@"width"
                    options:0
                    context:nil];
    
    return self;
}

This registers our instance of HelloWorldController as an observer to the _rectangle's "width" key path. Unlike notifications, you cannot choose which method gets called when the key path changes. All KVO change notifications call a method named observerValueForKeyPath:... on our object. Here's a simple implementation that just logs the key path:

- (void)observeValueForKeyPath:(NSString *)keyPath
                     fObject:(id)object
                      change:(NSDictionary *)change
                     context:(void *)context
{
    NSLog(@"Key path changed: %@", keyPath);
}

We've now fully setup KVO to watch when the width property of our rectangle changes. If we run the application, and change the width value in the user interface, you should see the log statements in your console output.

Generally, you don't have to use KVO directly, as it's more of a behind-the-scenes technology for bindings. All of Cocoa's controllers, including NSObjectController, uses KVO to be notified of changes to the content object, i.e. the model, and then updates the view with the new values, automatically. Thus, when we bound the area text field to "area" through NSObjectController, it set up a two way street between the text field and the key path. When the value of the text field changes, the text field notifies the controller. The controller then updates the model to the new value. Conversely, when the controller detects the model changed via KVO, it updates the view to the new value.

So how does this relate to area text field not being updated? The problem is that the area value is the width multiplied by the height. Thus, if the width changes, the area is also changed. The same goes for the height. Since Cocoa cannot possibly know the relationship between a rectangle's width, area, and height, we have to tell it.

What we have here is a situation called dependent keys. We have one key, area, that is dependent on two other keys, width and height. To setup dependent key relationships in the model, we need to implement a class method named +keyPathsForValuesAffecting<Key> in our Rectangle class. The implementation for the area key is as follows:

+ (NSSet *)keyPathsForValuesAffectingArea
{
    return [NSSet setWithObjects:@"width", @"height", nil];
}

This tells Cocoa that the width and height keys affect the area key. Since the perimeter is also dependent on width and height, it is also a dependent key and needs its own class method:

+ (NSSet *)keyPathsForValuesAffectingPerimeter
{
    return [NSSet setWithObjects:@"width", @"height", nil];
}

By setting up our dependent keys, our Rectangle model class is now truly KVC compliant. In practice, this changes the way KVO works. Thus, when the width changes, not only does the KVO mechanism send out a notification that the width has changed, but it also sends out notifications that the area and perimeter have changed. Thus, any observers registered for changes to "area" or "perimeter" are now properly notified when either width or height changes.

With these two class methods added to the implementation of our Rectangle class, our application should now work properly. Changing the area or width will automatically update the area and perimeter.

You may still be wondering what the big deal with bindings is. Sure, we were able to delete three methods in the controller, but we had to add two methods to the model. While we're keeping this example relatively short, as you create more complex applications, Cocoa bindings will save you a lot of coding.

Conclusion

We've covered how to use Cocoa bindings instead of custom controller code. Along the way we've learned about key-value coding (KVC) and key-value observing (KVO). We've only scraped the surface of bindings, KVC and KVO. There are a few more hairy details to these technologies, but we've covered the basics. Again, I highly recommend actually working through the steps in this article on your own to convert the pre-bindings application to use Cocoa bindings. If you are having issues getting bindings to work, you can download before and after projects from the MacTech website to compare your project to a functional project.


Dave Dribin has been writing professional software for over eleven years. After five years programming embedded C in the telecom industry and a brief stint riding the Internet bubble, he decided to venture out on his own. Since 2001, he has been providing independent consulting services, and in 2006, he founded Bit Maki, Inc. Find out more at http://www.bitmaki.com/ and http://www.dribin.org/dave/.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

1Password 6.3.3 - Powerful password mana...
1Password is a password manager that uniquely brings you both security and convenience. It is the only program that provides anti-phishing protection and goes beyond password management by adding Web... Read more
Sublime Text 3126 - Sophisticated text e...
Sublime Text is a sophisticated text editor for code, markup, and prose. You'll love the slick user interface, extraordinary features, and amazing performance. Features Goto Anything. Use Goto... Read more
ForkLift 3.0 Beta 2 - Powerful file mana...
ForkLift is a powerful file manager and ferociously fast FTP client clothed in a clean and versatile UI that offers the combination of absolute simplicity and raw power expected from a well-executed... Read more
OmniFocus 2.7.1 - GTD task manager with...
OmniFocus helps you manage your tasks the way that you want, freeing you to focus your attention on the things that matter to you most. Capturing tasks and ideas is always a keyboard shortcut away in... Read more
CleanApp 5.1.1 - Application deinstaller...
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
Together 3.6.1 - Store and organize all...
Together helps you organize your Mac, giving you the ability to store, edit and preview your files in a single clean, uncluttered interface. Features Smart storage. With simple drag-and-drop... Read more
Cloud 4.1.1 - File sharing from your men...
Cloud is simple file sharing for the Mac. Drag a file from your Mac to the CloudApp icon in the menubar and we take care of the rest. A link to the file will automatically be copied to your clipboard... Read more
Together 3.6.1 - Store and organize all...
Together helps you organize your Mac, giving you the ability to store, edit and preview your files in a single clean, uncluttered interface. Features Smart storage. With simple drag-and-drop... Read more
Cloud 4.1.1 - File sharing from your men...
Cloud is simple file sharing for the Mac. Drag a file from your Mac to the CloudApp icon in the menubar and we take care of the rest. A link to the file will automatically be copied to your clipboard... Read more
OmniFocus 2.7.1 - GTD task manager with...
OmniFocus helps you manage your tasks the way that you want, freeing you to focus your attention on the things that matter to you most. Capturing tasks and ideas is always a keyboard shortcut away in... Read more

Zip—Zap (Games)
Zip—Zap 1.01 Device: iOS Universal Category: Games Price: $1.99, Version: 1.01 (iTunes) Description: Touch to contract.Release to let go.Bring the clumsy mechanical beings home. · · · over 100 levelsno adsno in-app-purchases Zip—... | Read more »
Paperback: The Game (Games)
Paperback: The Game 1.0 Device: iOS Universal Category: Games Price: $3.99, Version: 1.0 (iTunes) Description: You are an author trying to finish kitschy paperback novels. Complete Westerns, Science Fiction, Romance or even a Crime... | Read more »
How to Rule With a Firm Hand in My Majes...
My Majesty is a kingdom management sim not unlike August’s magisterial hit, Reigns. It’s essentially a reskin of developer Tigrido’s previous management sim, Dictator. As supreme ruler of the land, you must consult with a number of subjects to... | Read more »
Our 5 Favorite iMessage Sticker Packs
At long last, iMessage joins the ranks of messaging apps the likes of LINE and Whatsapp, adding an impressive collection of stickers. They’re a great way to add a little something extra to your daily conversations. [Read more] | Read more »
How to get past Vulture Island's tr...
Vulture Island is a colorful and quirky mish-mash of platforming and puzzles. It’s creative and fresh, but sometimes the game can throw a curveball at you, leaving you stuck as to how you should progress. These tips will help you explore smoothly... | Read more »
The new Clash of Kings is just for Weste...
If you’ve played the original Clash of Kings, you’ll probably recognise the city building, alliance forging and strategic battles in Clash of Kings: The West. What sets this version apart is that it’s tailor made for a Western audience and the... | Read more »
Frost - Survival card game (Games)
Frost - Survival card game 1.12.1 Device: iOS Universal Category: Games Price: $3.99, Version: 1.12.1 (iTunes) Description: *Warning: the game will work on iPhone 5C and above and iPad Pro / 4. Other devices are not supported* | Read more »
How to build and care for your team in D...
Before you hit the trail and become a dog sledding legend, there’s actually a fair bit of prep work to be done. In Dog Sled Saga, you’re not only racing, you’re also building and caring for a team of furry friends. There’s a lot to consider—... | Read more »
How to win every race in Dog Sled Saga
If I had to guess, I’d say Dog Sled Saga is the most adorable racing game on the App Store right now. It’s a dog sled racing sim full of adorable, loyal puppies. Just look at those fluffy little tails wagging. Behind that cute, pixelated facade is... | Read more »
Let the war games commence in Gunship Ba...
Buzz Lightyear famously said, “This isn’t flying, this is falling – with style!” In the case of Gunship Battle: Second War, though, this really is flying - with style! The flight simulator app from Joycity puts you in control of 20 faithfully... | Read more »

Price Scanner via MacPrices.net

15-inch 2.2GHz Retina MacBook Pro on sale for...
B&H Photo has the 2015 15″ 2.2GHz Retina MacBook Pro (MJLQ2LL/A) on sale for $1799, including free shipping plus NY sales tax only. Amazon also has the 2015 15″ 2.2GHz Retina MacBook Pro (... Read more
Toughbook Celebrates 20 Years of Ruggedized M...
Panasonic System Communications Company of North America, Division of Panasonic Corporation of North America (Panasonic) today celebrates the 20th anniversary of its industry-leading Toughbook mobile... Read more
12-inch 1.1GHz Gray Retina MacBook on sale fo...
B&H Photo has the 2016 12″ 1.1GHz Gray Retina MacBook on sale for $1199.99 including free shipping plus NY sales tax only. Their price is $100 off MSRP. Read more
13-inch 2.5GHz MacBook Pro (Apple refurbished...
Apple has Certified Refurbished 13″ 2.5GHz MacBook Pros available for $829, or $270 off the cost of new models. Apple’s one-year warranty is standard, and shipping is free: - 13″ 2.5GHz MacBook Pros... Read more
Save 30% on Camera Guard’s Secure Protection...
To celebrate the release of macOS Sierra, Miami-based security solutions company, ProtectStar has announced a special 30% discount on Camera Guard Professional for Mac 2016. This innovative security... Read more
DVDFab Special Deal – Get a 1-Year Free Licen...
Beijing, China based specialist in the field of DVD, Blu-ray and video backup solutions, Fengtao Software has launched its Autumn Special Deals 2016, giving a 1-year free license of a randomly picked... Read more
21-inch iMacs on sale for up to $120 off MSRP
B&H Photo has 21″ iMacs on sale for up to $120 off MSRP including free shipping plus NY sales tax only: - 21″ 3.1GHz iMac 4K: $1379 $120 off MSRP - 21″ 2.8GHz iMac: $1199.99 $100 off MSRP - 21″ 1... Read more
13-inch 2.7GHz/256GB Retina MacBook Pro on sa...
Amazon.com has the 13″ 2.7GHz/256GB Retina Apple MacBook Pro on sale for $151 off MSRP including free shipping: - 13″ 2.7GHz/256GB Retina MacBook Pro (sku MF840LL/A): $1348 $151 off MSRP Read more
Apple TVs on sale for up to $50 off MSRP
Best Buy has 32GB and 64GB Apple TVs on sale for $40-$50 off MSRP on their online store. Choose free shipping or free local store pickup (if available). Sale prices for online orders only, in-store... Read more
Apple refurbished 13-inch Retina MacBook Pros...
Apple has Certified Refurbished 13″ Retina MacBook Pros available for up to $270 off the cost of new models. An Apple one-year warranty is included with each model, and shipping is free: - 13″ 2.7GHz... Read more

Jobs Board

*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
Restaurant Manager (Neighborhood Captain) - A...
…in every aspect of daily operation. WHY YOU'LL LIKE IT: You'll be the Big Apple . You'll solve problems. You'll get to show your ability to handle the stress and Read more
Sr. *Apple* Mac Engineer - Net2Source Inc....
…staffing, training and technology. We have following position open with our client. Sr. Apple Mac Engineer6+ Months CTH Start date : 19th Sept Travelling Job If Read more
*Apple* Retail - Multiple Positions-Norfolk,...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
Restaurant Manager (Neighborhood Captain) - A...
…in every aspect of daily operation. WHY YOU'LL LIKE IT: You'll be the Big Apple . You'll solve problems. You'll get to show your ability to handle the stress and Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.