TweetFollow Us on Twitter

The Road to Code: Writing Even Less Code

Volume Number: 24
Issue Number: 12
Column Tag: The Road to Code

The Road to Code: Writing Even Less Code

Introduction to Core Data

by Dave Dribin

Core Data

Our topic this month is Core Data, a technology introduced in Mac OS X 10.4 by Apple to help reduce time spent writing code. Interface Builder allows you to setup view classes with out writing any custom layout code. Cocoa bindings allow you tie together the view and the model without writing any custom controller code. What's left? The model. Core Data is an attempt to allow developers to write model classes without writing any code.

Core Data was introduced in Mac OS X 10.4 and heavily enhanced in OS X 10.5. In a nutshell, Core Data provides automatic object persistence and object lifecycle management. The phrase object persistence is a fancy way of saying "saving objects to a file," similar to archiving. Core Data provides more, though. It allows you to find and filter objects and provides integration with the user interface, such as automatic undo support. It can optionally store your objects to a single-user relational database for increased performance.

It is common to think of Core Data as an object-relational mapping (ORM) tool. ORM tools automatically persist objects to a relational database. ORM tools allow you to save, find, and update objects using SQL statements. SQL is the language of relational databases, and ORM tools typically generate the SQL select, update and insert statements for you. Some popular ORM tools in the Java world are Hibernate and Enterprise Java Beans (EJB), and Active Record is the ORM tool for Ruby on Rails.

However, there is one very important difference between most ORM tools and Core Data. Most ORM tools are designed for multi-user and high-concurrency environments, such as the backend of web application. Core Data is designed with the single-user desktop application in mind. You'll see some of the ramifications of this as you delve into the architecture and use cases of Core Data. For example, Core Data does not require use of a relational database.

A Bit of History

Before digging into the details of Core Data, it is useful to understand where it came from. It turns out that Apple has it's own web-based ORM tool as part of WebObjects. WebObjects is a complete web application framework. It started life way back in the early 1990s at NeXT written in Objective-C and was later rewritten in Java. When Apple acquired NeXT, mainly for Mac OS X, it also acquired WebObjects. Today, Apple uses WebObjects for its online store and the server side components of the iTunes Music Store. It is also available for free for developers to use and has quite a strong, albeit small, following.

WebObjects includes an ORM tool called Enterprise Objects Frameworks, or EOF. It allows automatic persistence of Java model objects to a relational database. As far as ORM tools go, EOF is quite powerful and feature filled. However, since it is now only available in Java, it is not something that can be used directly by Cocoa applications.

Apple used its experience with EOF to design Core Data. It took many of the concepts of EOF and pared them down for single-user applications. There is no direct link between EOF and Core Data, just a foundation of ORM experience. Unfortunately, not all of the features of EOF were ported to Core Data, so sometimes we are left wanting.

Terminology

Core Data introduces some new terminology, mainly borrowed terminology from the database world. Databases are modeled using the entity-relationship model, thus Core Data objects are also modeled using the entity-relationship model. But don't fret, most of these terms correlate to terms you already know.

In an entity-relationship model, a class is called an entity. And just classes contains instance variables, entities contain properties. There are two kinds of properties: attributes and relationships. Attributes are properties consisting of simple data types, such as strings, numbers, and dates. Relationships are properties that link together multiple entities. We won't be covering relationships in this article.

Okay, enough of the jibber jabber, let's get on to writing a Core Data application.

Core Data Version of Rectangles

We are going to convert the Rectangles application that we have built up over the last few months to a Core Data application. Using Core Data will allow us to remove almost all of our code. We can't quite get rid of all of it, but the reduction is still dramatic.

We're going to start off my creating a new project. Create a Core Data Document-based Application from the New Project window, as shown in Figure 1.


Figure 1: New Core Data project

This project looks similar to standard document-based applications, but it has an additional group in the Groups & Files list called Models, as shown in Figure 2. In that group is a file named MyDocument.xcdatamodel. This file extension is short for "Core Data data model" and represents the entity-relationship model for our application.


Figure 2: Groups and Files

We're going to dive right in by working on our data model first. We want to create a rectangle entity with width and height attributes. If you double click on the data model, you will be presented with what is known as the Core Data modeler, as shown in Figure 3. We will design our data model graphically without writing any code or text.


Figure 3: Core Data modeler

Click the + button in the Entity pane to create a new entity. Use the detail view on the right side of the window to rename the entity to Rectangle, as shown in Figure 4.


Figure 4: Rectangle entity

Next, add a new attribute by making sure the Rectangle entity is still selected and click the + button in the Property pane. Rename the attribute to width, uncheck the Optional checkbox, change the Type to Float, and set the Default Value to 0. Now repeat this process to create a height attribute. The result should look like Figure 5.


Figure 5: Height and width attributes

In the bottom half of the window, you will see a graphical representation of our entity-relationship model, as shown in Figure 6. This graphical representation is called an entity-relationship diagram or ER diagram. It is common to model relational databases using an ER diagram, again showing the database roots of Core Data.


Figure 6: Rectangle ER diagram

With that, we're done with our data model. We can now save, load, and use Rectangle entities in our application. But, we haven't written any code, yet. How can you use an entity without any code? Core Data provides built-in classes to make this easy.

Core Data Class Architecture

When you created the Rectangle entity, you'll notice that one of the fields is named Class and, by default, is set to NSManagedObject. Every instance of a Core Data entity is represented by an instance of NSManagedObject. In a way, it is similar to how NSObject is the root class of all Objective-C classes. However, you do not need to subclass NSManagedObject to use it. So how do you access the properties? Using key-value coding.

When an NSManagedObject, or managed object for short, gets instantiated, it's initialized for a particular entity. The managed object reads your data model and allows you to access the attributes using the standard KVC valueForKey: and setValue:forKey: methods. For example, say we have an instance of NSManagedObject representing our Rectangle entity. You would access the width attribute like:

    NSManagedObject * rectangle = ...;
    NSNumber * width = [rectangle valueForKey:@"width"];
    NSLog(@"Width is: %f", [width floatValue]);

But for now, we do not need to use our entity from code. We will be using Cocoa bindings to connect managed objects to the user interface, which uses KVC.

Another important class in the Core Data architecture is NSMangedObjectContext, or managed object context. You use the managed object context to find, create, and delete managed objects. Every managed object instance is owned by the managed object context that created it. The managed object context also contains unsaved changes. Thus, when the user makes their edits, these changes occur only the managed object context. To save these changes to disk, you tell the managed object context to save.

The final important class in the Core Data architecture is NSPersistentStoreCoordinator, or persistent store coordinator. A persistent store coordinator links a managed object context to a specific file on disk, called a persistent store. There are three types of persistent stores that defines the internal file format on disk: XML, binary, and SQLite. The XML persistent store is nice because you can use any text editor to examine the contents. The binary persistent store is faster and more efficient, but it is a proprietary file format. The SQLite persistent store type is the most interesting.

SQLite is a full relational database system that is self-contained and serverless. Most relational database systems, like PostgreSQL, MySQL, or Oracle, require a separate server process to run in the background. A server-based relational database requires a lot of extra setup and can be a little difficult to maintain. SQLite on the other hand is linked in as part of your application and the database is stored in a single file on disk. You access the data using standard SQL queries. SQLite cannot handle highly concurrent multi-user applications like busy web applications, but it is perfect for a single user application, like an OS X application.

When Core Data uses a SQLite persistent store, it works very much like traditional ORM tools. It generates SQL statements to create, save, and find objects in the database. Core Data does not allow you to interact with SQLite directly, though. It hides all the SQL from the programmer.

The benefit of using a SQLite persistent store type is that it is much more efficient than the XML and binary types. Only the objects that you are using get loaded into memory. Thus if the user's data contains a large amount of entities, SQLite will only load the objects the user is using, saving memory. Also, finding and filtering managed objects are more efficient since Core Data can use SQL to find the objects. Databases are designed to perform searches very efficiently.

Generally, you use the XML persistent store type during the early stages of development, because the file can be read with a text editor. However, SQLite is often the best case for a released application since it is more efficient. There are some subtle differences between the persistent store types, so if you do change persistent store types, be sure to thoroughly test after the changes. Also, if you change the type, your application will not be able to load previously saved files.

I admit that the Core Data architecture is somewhat complex. However, if you are writing a document-based application, your life is simplified. Apple provides an NSDocument subclass for document-based applications that use Core Data called NSPersistentDocument. The NSPersistentDocument takes care of setting up the persistent store coordinator and managed object context for you. When you save your document, it saves the context to the user's file. Since we chose a Core Data Document-based Application, our MyDocument class is automatically a NSPersistentDocument.

Configuring the Document Type

Let's now setup the document type for our application, so we can save and open files. Double-click on the Rectangles target to open the Info window for this target. You'll notice there are three document types already present, one for each of the different persistent store types. We'll use SQL type, so delete the other two. Change the Name to Rectangles Document and the extension to rectanglescd. We already used the rectangles extension in our previous, non-Core Data application, so we want to choose a different one to avoid a conflict.

Also change the Identifier to something unique. All of these changes are summarized in Figure 7.


Figure 7: Document types

Laying out the User Interface

We're now going to switch to Interface Builder to layout the interface and setup our Cocoa bindings. Remove the initial text label, and add a table view. Rename the columns to Width and Height. Drag number formatters on each of the columns and set their Style to Decimal. Now add two buttons underneath the table, one named Add the other named Remove. Setup the autosizing appropriately. The final result should look like Figure 8.


Figure 8: Initial window layout

Next, we're going to create an array controller for our Cocoa bindings. Drag an array controller from the Library to our xib document window, and rename it to Rectangles, as shown in Figure 9.


Figure 9: Array controller

Now setup the array controller's attributes to match Figure 10. You need to set the Mode to Entity, set the Entity Name to Rectangle, and check the Prepares Content checkbox. This puts the array controller into Core Data mode and will hold instances of NSManagedObject of the given entity name.


Figure 10: Array controller attributes

Our array controller needs a managed object context to fetch and edit managed objects. Remember that NSPersistentDocument sets up a context for each document. The File's Owner for the MyDocument.xib is our MyDocument subclass of NSPersistenDocument. We can bind the array controller's Managed Object Context to the managedObjectContext of File's Owner, as shown in Figure 11.


Figure 11: Array controller bindings

With our array controller in place, we can now bind the Width and Height columns. Just as we did for our non-Core Data application, bind the Width column to the Rectangles array controller. Set the Controller Key to arrangedObjects and the Model Key Path to width, as shown in Figure 12. Make similar bindings for the Height column, except using height as the Model Key Path.


Figure 12: Width column bindings

Finally, hookup the Add button to the Rectangles array controller's add: action and the Remove button to the remove: action. Bind the Add button's Enabled binding to the canAdd Controller Key and bind the Remove button's Enabled binding to the canRemove Controller Key.

At this point, we have a fully functional application. You should be able to add and remove rectangles from the table using the buttons, and you should be able to save and open documents. Furthermore, you will notice that you get dirty document and undo support. Now I'd like to remind you again that we haven't written any code. This is the promise of Core Data.

Of course our application is missing a few things from our non-Core Data version. First, the default width for new rectangles was 15 and the default height was 10. In our non-Core Data version, we created our own subclass of array controller and overrode the newObject method. We can certainly do that, but for now, I'm going to just change the default values in the data model, to keep with the "no code" theme.

The other major part missing is the area and perimeter table columns. Unfortunately, we are going to have to create some code to get these working. Because these are computed properties, we cannot add them in the data model. We're going to have to create a subclass of NSManagedObject for our Rectangle entity. Fortunately, Xcode can give us a head start. Make sure that your data model is selected in the Groups & Files list and click on File > New File to create a new file. Click on Managed Object Class in the New File window, as shown in Figure 13.


Figure 13: Generate managed object

Click Next as the Location and Target defaults should be correct. In the final screen, make sure that the Rectangles entity is selected as shown in Figure 14 and click Finish. This will create Rectangle.h and Rectangle.m files. You may want to move them in the Models group along with your data model file so you remember that they are managed objects.


Figure 14: Generate Rectangle class

If you look at the header, you'll see just a couple of @property declarations. There are no instance variables. Remember that the values are actually stored by the NSMangedObject superclass using KVC, so we don't need any instance variables in our subclass. These properties provide convenient, type-safe access to the entity's properties. The implementation file uses the @dynamic property keyword instead of the @synthesize. This is again because there are no instance variables.

Note that the width and height are of type NSNumber, even though we set them as Float in the data model. Core Data only deals with objects. If you want to get the actual float value, you need to use the floatValue method of NSNumber. Conversely, if you want to set the float value, you need wrap a float in an NSNumber:

    // Get the width as a float
    float width = [rectangle.width floatValue];
    // Set the width from a float
    rectangle.width = [NSNumber numberWithFloat:30.0];

Now that we have a real class, we can add our area and perimeter properties. Just as in our non-Core Data application, they are read-only. Listing 1 shows the full header with the property declarations:

Listing 1: Rectangle.h

#import <CoreData/CoreData.h>
@interface Rectangle :  NSManagedObject  
{
}
@property (retain) NSNumber * width;
@property (retain) NSNumber * height;
@property (readonly) float area;
@property (readonly) float perimeter;
@end

I used float here because it is easier to deal with primitive types when doing math. The implementation of these properties is similar to our non-Core Data application. We do have to convert the width and height to floats so we can perform math on them, though. The key-value observing methods are the same, however. Listing 2 shows the full implementation file.

Listing 2: Rectangle.m

#import "Rectangle.h"
@implementation Rectangle 
@dynamic width;
@dynamic height;
- (float)area
{
    return ([self.width floatValue] *
            [self.height floatValue]);
}
+ (NSSet *)keyPathsForValuesAffectingArea
{   
    return [NSSet setWithObjects:@"width", @"height", nil];
}
- (float)perimeter
{
    return ((2*[self.width floatValue]) +
             (2*[self.height floatValue]));
}
+ (NSSet *)keyPathsForValuesAffectingPerimeter
{
    return [NSSet setWithObjects:@"width", @"height", nil];
}
@end

And that's all the code we need. Now, we just need to add the remaining elements to the user interface.

Creating the Final User Interface

Switch to Interface Builder to complete the changes. Add two more columns to the table view and label them Area and Perimeter, respectively. Make sure they are marked as uneditable and add Decimal number formatters to both. Bind them to arrangedObjects.area and arrangedObjects.perimeter, respectively, of the Rectangles array controller. Figure 15 shows the bindings for the Area column.


Figure 15: Area column bindings

Add four labels at the bottom left side of the table for the Total Area: and Total Perimeter: fields. Two will be used for static text and two will be used for the actual values. For the two used for the values, add Decimal number formatters. The final window layout should look like Figure 16.


Figure 16: Final window layout

We are now going to setup the bindings using collection operators, just like we did in the non-Core Data application. Bind the total area value label to the Rectangles array controller. Set Controller Key to arrangedObjects and the Model Key Path to @sum.area. The result should look like Figure 17. Next, bind the total perimeter value field to the @sum.perimeter Model Key Path.


Figure 17: Total area bindings

We are now finished with our changes. You should be able to run the application and have the area and perimeter user interface elements work appropriately. Our application is nearly identical to our non-Core Data application, and all we had to do was write four methods for the area and perimeter. And this includes support for undo! Not to shabby.

Caveat Emptor

There is one subtle difference from our non-Core Data application. In our previous application, we store an array of rectangles in the document. This means that as we added rectangles, their order is preserved. Core Data uses sets, instead of arrays, which means they are unordered. But surely they have to show up in some order in the table. Unordered just means the order is not consistent. The list of rectangles may show up in a different order every time you load the file. Since the user can sort the table by clicking on one of the table columns, the easy way to fix the problem is to always sort the table by a column, by default. To truly remember the order of the items as they were entered is a very difficult Core Data problem, though. And even though setting up default column sorting is significantly easier, it is still beyond the scope of this article. For now, we'll just have to deal with that limitation.

Also, this article just scratches the tip of the Core Data iceberg. There are many more advanced topics, such a relationship properties, threading techniques, and migration between different versions of your model. Apple's documentation has a lot of information on these topics.

Conclusion

Even though Core Data is a complicated subject with some limitations, it is still a hugely valuable asset to Mac OS X development. It can save you from writing a lot of your own model code, just as Cocoa bindings can save you from writing a lot of controller code. It won't eliminate code completely, but I urge you to at least investigate Core Data for your future applications.


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/.

 
AAPL
$94.72
Apple Inc.
+0.78
MSFT
$44.83
Microsoft Corpora
-0.01
GOOG
$594.74
Google Inc.
+5.27

MacTech Search:
Community Search:

Software Updates via MacUpdate

Macs Fan Control 1.1.12 - Monitor and co...
Macs Fan Control allows you to monitor and control almost any aspect of your computer's fans, with support for controlling fan speed, temperature sensors pane, menu-bar icon, and autostart with... Read more
A Better Finder Rename 9.37 - File, phot...
A Better Finder Rename is the most complete renaming solution available on the market today. That's why, since 1996, tens of thousands of hobbyists, professionals and businesses depend on A Better... Read more
MacBook Air EFI Firmware Update 2.9 - Fo...
MacBook Air EFI Firmware Update is recommended for MacBook Air (Mid 2011) models. This update addresses an issue where systems may take longer to wake from sleep than expected and fixes a rare issue... Read more
FileZilla 3.9.0.1 - Fast and reliable FT...
FileZilla (ported from Windows) is a fast and reliable FTP client and server with lots of useful features and an intuitive interface.Version 3.9.0.1: MSW: Fix installation issue with locked DLLs... Read more
OS X Yosemite 10.10 DP4 - Developer Prev...
Note: This is a Developer Preview. You must be a registered Apple Mac Developer to download this update. OS X Yosemite is Apple's newest operating system for Mac. An elegant design that feels... Read more
FinderPop 2.5.6 - Classic Mac utility, n...
FinderPop is a Universal preference pane that extends OS X's contextual menus using a FinderPop Items folder much as the Apple Menu Items folder used to do for the Apple menu. It has other features... Read more
SpiderOak 5.1.7 - Secure cloud backup, s...
SpiderOak is a multi-platform secure online backup, storage, access, and sharing solution engineered for the consumer and small businesses. You must first sign up to use SpiderOak. Running natively... Read more
Espionage 3.6 - Simple, state of the art...
Espionage offers state-of-the-art encryption and plausible deniability for your confidential data. Sometimes, encrypting your data isn't enough to protect it. That's why Espionage 3 goes beyond data... Read more
calibre 1.45.0 - Complete e-library mana...
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... Read more
iFFmpeg 4.3.1 - Convert multimedia files...
iFFmpeg is a graphical front-end for FFmpeg, a command-line tool used to convert multimedia files between formats. The command line instructions can be very hard to master/understand, so iFFmpeg does... Read more

Latest Forum Discussions

See All

Celebrate Summer With a Cat in the Hat L...
Celebrate Summer With a Cat in the Hat Learning Library Sale Posted by Ellis Spice on July 22nd, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
MyTaskList Review
MyTaskList Review By Jennifer Allen on July 22nd, 2014 Our Rating: :: EFFECTIVE IF PLAINUniversal App - Designed for iPhone and iPad It’s not the most stylish of task management apps, but MyTaskList has all the features you could... | Read more »
FlyCraft Herbie: Crazy Machines Review
FlyCraft Herbie: Crazy Machines Review By Jennifer Allen on July 22nd, 2014 Our Rating: :: TRICKY FLYINGUniversal App - Designed for iPhone and iPad A tough game of careful thrusting and navigation, FlyCraft Herbie: Crazy Machines... | Read more »
MTN Review
MTN Review By Jessica Fisher on July 22nd, 2014 Our Rating: :: ADORABLE, SERENE, AND AMUSINGUniversal App - Designed for iPhone and iPad MTN is an adorable, talking pet mountain that is less game and more zen garden.   | Read more »
Fly High with Ninja UP! Now Available o...
Fly High with Ninja UP! Now Available on the App Store Posted by Jessica Fisher on July 22nd, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Bio Inc. Review
Bio Inc. Review By Nadia Oxford on July 22nd, 2014 Our Rating: :: SICKENING - IN A COMPELLING WAYUniversal App - Designed for iPhone and iPad Bio Inc is about orchestrating the medical destruction of a single person. If that doesn’... | Read more »
HELMUT Review
HELMUT Review By Andrew Fisher on July 21st, 2014 Our Rating: :: TRUNDLE SIMULATOR 2014Universal App - Designed for iPhone and iPad HELMUT is a fun, fleeting time-sink that offers a momentary distraction and nothing else.   | Read more »
Walkr Review
Walkr Review By Jennifer Allen on July 21st, 2014 Our Rating: :: ORIGINAL WALKINGiPhone App - Designed for the iPhone, compatible with the iPad Walking is a bit more exciting thanks to this planet building/discovering sim reliant... | Read more »
Zombie Commando Review
Zombie Commando Review By Jennifer Allen on July 21st, 2014 Our Rating: :: MINDLESS SLAUGHTERUniversal App - Designed for iPhone and iPad Briefly fun but ultimately forgettable, Zombie Commando will scratch an itch then be... | Read more »
Swords & Poker Adventures Review
Swords & Poker Adventures Review By Jennifer Allen on July 21st, 2014 Our Rating: :: SOULLESS POKER PLAYUniversal App - Designed for iPhone and iPad Swords & Poker Adventures is a mishmash of Poker and RPGing, but it lacks... | Read more »

Price Scanner via MacPrices.net

15-inch 2.0GHz MacBook Pro Retina on sale for...
B&H Photo has the 15″ 2.0GHz Retina MacBook Pro on sale for $1829 including free shipping plus NY sales tax only. Their price is $170 off MSRP. B&H will also include free copies of Parallels... Read more
Apple restocks refurbished Mac minis for up t...
The Apple Store has restocked Apple Certified Refurbished Mac minis for up to $150 off the cost of new models. Apple’s one-year warranty is included with each mini, and shipping is free: - 2.5GHz Mac... Read more
Twelve South HiRise For MacBook – Height-Adju...
If you use your MacBook as a workhorse desktop substitute, as many of us do, a laptop stand combined with an external keyboard and pointing device are pretty much obligatory if you want to avoid... Read more
Why The Mac Was Not Included In The Apple/IBM...
TUAW’s Yoni Heisler cites Fredrick Paul of Network World whoi blogged last week that the Mac’s conspicuous absence from Apple and IBM’s landmark partnership agreement represents a huge squandered... Read more
Save $100 on 13-inch Retina MacBook Pros, plu...
Adorama has 13″ Retina MacBook Pros on sale for $100 off MSRP. Shipping is free, and Adorama charges sales tax in NY & NJ only: - 13″ 2.4GHz/128GB MacBook Pro with Retina Display: $1199 - 13″ 2.... Read more
Blurr it 2.3 for iOS – Quickly Blurs Selected...
Hyderabad, India based TouchLabs has announced a new update of Blurr it 2.3, their photography app for iOS users. Blurr it allows you to blur part of the image to hide potentially sensitive or... Read more
MacBook Airs on sale for $100 off MSRP, start...
Best Buy has the new 2014 MacBook Airs on sale for up to $100 off MSRP on their online store. Choose free home shipping or free local store pickup (if available). Prices valid for online orders only... Read more
Amazon Announces Kindle Unlimited: Unlimited...
Amazon.com has introduced Kindle Unlimited — a new subscription service which allows customers to freely read as much as they want from over 600,000 Kindle books, and listen as much as they want to... Read more
New Linksys Wireless Range Extenders Boost Wi...
Linksys has announced its new lineup of Linksys Wi-Fi Range Extenders. Consumers often experience a weak wireless signal in some parts of their house or apartment caused by blocking elements such as... Read more
MacBook Airs available starting at $719
The Apple Store has Apple Certified Refurbished 2013 & 2012 MacBook Airs in stock today starting at $719. An Apple one-year warranty is included with each MacBook, and shipping is free: 2013... Read more

Jobs Board

*Apple* Systems Administrator - DISH (United...
…satellite service provider, and Dish is currently looking for an experienced Apple /Mac Systems Administrator. Apple systems administrator will be responsible for Read more
*Apple* Systems Administrator - DISH (United...
…satellite service provider, and Dish is currently looking for an experienced Apple /Mac Systems Administrator. Apple systems administrator will be responsible for Read more
*Apple* /MAC Systems Administrator - Tekkies,...
Title: Apple /Mac Systems Administrator Location: Englewood, CO Term: Permanent Placement Description Our client is currently looking for an experienced Apple /Mac Read more
*Apple* Support Specialist - Expert Technica...
Apple Support Specialist Expert Technical has an immediate opening for an Apple Support Specialist with an industry leading creative and graphic arts company in Read more
Sales Director, GAM - *Apple* - AMD (United...
This position will be responsible for managing the Apple relationship with AMD. Ideally, the candidate has a good understanding of Apple 's products and Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.