TweetFollow Us on Twitter

Introduction to Core Data, Part III

Volume Number: 21 (2005)
Issue Number: 11
Column Tag: Programming

Introduction to Core Data, Part III

Fetch, Clarus, Fetch

by Jeff LaMarche

In the previous two Core Data articles, we discussed how to create a data model and how to create, delete, and edit data instances both directly from the user interface and programmatically. Those two articles covered most of what you'll need to do with your application data models. Most. But not all. There's one important area that those articles didn't touch on.

In the prior articles, all instances of data that we worked with were ones that we created, or else were ones that the user took some action upon, so we never had to worry about what piece of data we were going to work with. There are times, however, when you will need to retrieve a piece of data from your application's context, but instead of knowing, for example, that the user clicked on the row that corresponds to a specific data instance, you'll know only some criteria about that data. For example, you might need to find a book instance based on its title or, perhaps, to find all the books by a particular author or publisher, or to find all the books published in a given year.

Now, certainly, you could create an NSArrayController in Interface Builder to represent all the instances of a particular entity type and then manually loop through the items it manages to look for the instances that meet your criteria, but that would be inefficient and involve a lot of unnecessary work. You're much better off using the tools Apple gives you for this purpose.

Fetch Requests

The mechanism that you use to retrieve data programmatically from your application's or document's context is called (appropriately) a Fetch Request, and is represented by the Cocoa class NSFetchRequest. To use this class, you instantiate a request object then provide it with the criteria that describes the object or objects that you want to retrieve. After that, you execute the fetch request, and it returns to you an array of NSManagedObjects that match those criteria. Fetch requests exist in both Enterprise Objects Framework (EOF) and in Core Data, and function very similarly, so if you've used EOF fetch requests or another object-relational mapping tool like Hibernate or Cayenne, this process will seem familiar to you.

Enterprise Objects has a very handy class of utility methods called EOUtilities which encapsulates a lot of commonly used functionality into public static methods (Java's equivalent to Objective-C's class methods) that can be called from anywhere. Core data has no comparable class, despite using a nearly identical mechanism for retrieving data.

So, this month, instead of building a whole new application, we're going to implement a class similar to EOUtilities (but much less extensive) called MTCDUtilities (that stands for MacTech Core Data Utilities, if you were wondering). The methods we are going to implement will make it easier to retrieve data from your context and also, in the process, will show you how to find and retrieve data from your application's context based on criteria. MTCDUtilities will have four class methods (much less than EOUtilities) and no instance variables, so you will never instantiate an MTCDUtilities object, but rather simply call methods on the class object.

The complete class we're building is available on the MacTech FTP site, with complete HeaderDoc documentation. Before we learn how to specify criteria for retrieving data, let's take a quick look at retrieving data without criteria. When you do not specify any criteria, a fetch request retrieves all the existing instances of a given entity. The first of our four convenience methods does exactly that. Before we write it, let's create our class' header, with declarations of the methods we're going to be writing.

MTCDUtilities.h

This is the header for MTCDUtilities. There are no instance variables, just four class method declarations. To save space, I have not included the HeaderDoc comments.

#import <Cocoa/Cocoa.h>
#define MTTooManyEntitiesReturnedException 
   @"Too many entities returned"
#define MTCoreDataExeption @"Core Data exception"
@interface MTCDUtilities : NSObject 
{
}
+(NSArray *)objectsForEntityNamed:(NSString *)name
   inContext:(NSManagedObjectContext *)context;
+(NSArray *)objectsForEntityNamed:(NSString *)name 
   matchingKey:(NSString *)key
   andValue:(id)value 
   inContext:(NSManagedObjectContext *)context;
+(NSManagedObject *)objectForEntityNamed:(NSString *)name 
   matchingKey:(NSString *)key andValue:(id)value 
   inContext:(NSManagedObjectContext *)context;
+(NSArray *)objectsForEntityNamed:(NSString *)name
   matchingKeysAndValues:(NSDictionary *)keyValues 
   usingOR:(BOOL)useOR
   inContext:(NSManagedObjectContext *)context;
@end

For now, don't worry about any of the methods except for objectsForEntityNamed:inContext:, which we'll be writing first. We'll write the rest after we look at how to specify criteria.

MTCDUtilities.m / objectsForEntityNamed:inContext:

This method returns all instances of a given entity.

+(NSArray *)objectsForEntityNamed:(NSString *)name 
   inContext:(NSManagedObjectContext *)context
{

   // We have to tell the Fetch Request which entity instances to retrieve. We do
   // that using an NSEntityDescription based on the parameter name.
   NSEntityDescription *entity = [NSEntityDescription 
      entityForName:name inManagedObjectContext:context];

   // Next, we declare an NSFetchRequest, and give it the entity description
   NSFetchRequest *req = [[NSFetchRequest alloc] init];	[req setEntity:entity];

   // Before executing the fetch request, we declare an NSError
   // which is used to find out about any problems encountered
   // executing the request

   NSError *error = nil;

   // We next supply it (by reference) when we execute the request

 NSArray *array = [context executeFetchRequest:req 
      error:&error];

   // A nil array tells us something went wrong

if (array == nil)
   {
      // We instantiate an exception using the error description from
      // NSError, then raise the exception, which stops execution

NSException *exception = [NSException 
         exceptionWithName:MTCoreDataExeption 
         reason:[error localizedDescription] 
         userInfo:nil];

      // Since execution will stop when we raise the exception, we
      // need to release any memory before we do so. 

      [req release];
      [exception raise];
   }

   // We allocated it, we have to release it

[req release];

   // Return the result set returned from executing the fetch request

   return array;
}

Now, any time we want to retrieve all the instances of a given entity, we can simply do something like:

NSArray *array = [MTCDUtilities 
   objectsForEntityNamed:@"Book" 
   inContext:context];

Predicates and Format Strings

That's all well and good, but when you want execute a fetch request to retrieve less than all of the instances, you're going to need to tell the request exactly which data instances you want to retrieve. The way that criteria are specified in Core Data is by way of something called a predicate, which is represented by the class NSPredicate and its subclasses.

An example of a predicate, in plain English, is "Name is 'Joe'" or "Age is less than 21". Predicates can be more complex, however, such as "Name is 'Joe' or 'Fred' or begins with the letter "I" and age is less than 21 or parent has given permission." The latter is an example of a compound predicate. Predicates are completely distinct from entities and fetch requests. You can create one predicate, say, "Name is 'Joe'" and use it to retrieve all the People entities with a name of Joe and then turn around and use the same predicate to retrieve all the Dog entities named Joe. Predicates don't know or care one whit about the entities they are being used to retrieve.

Predicates can be assembled programmatically by creating expressions, arguments, and substitution variables and compounding them into predicates. You will rarely, if ever, do this, however, because Apple has provided a much nicer mechanism for creating predicates: the Format String.

You've used format strings in Cocoa before. NSString has a similar mechanism that allows you to assemble multiple strings, raw datatypes, and Objective-C object instances into a single NSString using stringWithFormat:. The format strings used by NSPredicate are similar, but not exactly the same, as those used by NSString. Both types of format strings use substitution variables, but NSPredicate adds a bit of functionality. Because the format strings used to specify criteria has its roots in SQL (Structured Query Language--the language used to retrieve data from databases), internally it is fairly picky about certain language constructs. For example, if you're comparing a string attribute to a constant value, the constant value must be contained within single quotes.

For the most part, however, if you use format strings to create your predicates, you won't have to worry about such things. When you use NSPredicate's predicateWithFormat: method, it will automatically do the right thing based on the type of object you pass in as a substitution variable. If you give it a string, it will add the appropriate quotes. If you give it a date, it will translate it into the appropriate date string for comparison. If you give it an NSNumber, it will leave the quotes off..

Despite the fact that it is very savvy about dealing with substitution variables, NSPredicate is still pickier about format strings than is NSString. There are a limited number of operators that you can use to create a valid predicate. You can use all the major C and SQL operators that you are accustomed to, such as == or = for an equals comparison, > for greater than, < for less-than, != or <> for not equal to, >= for greater than or equal to, and <= for less than or equal to. You can also use AND (or &&), OR (or ||), or NOT (or !) to compound phrases in your format string.

Here are a few examples of creating a predicate using a format string:

NSPredicate *pred1 = 
   [NSPredicate predicateWithFormat:@"age <= 21"];
NSPredicate *pred2 = [NSPredicate predicateWithFormat:
   @"name == %@ OR name == %@", @"Mary", @"Joe"];

Note that in the first example, I'm using a constant value (21) right in the format string. You can do this with numbers safely, but not with strings. You could not, for example, do this:

NSPredicate *pred = [NSPredicate 
   predicateWithFormat:@"name == Bob"];

Why? Well, remember what I said earlier about NSPredicate being picky internally about things like quotes around strings? If you don't use substitution variables, you're responsible for making sure that your constant matches NSPredicate's internal format requirements. Since attribute names cannot be made up of solely numbers, there's no chance of confusion between a number constant and an attribute name or reserved keyword. The same isn't true for string constants.

In general, it's safer to always use substitution variables, even when you're using a constant value. If you're curious, the following code will work (notice the single quotes), though I don't recommend you make a habit of creating predicates this way:

NSPredicate *pred = [NSPredicate 
   predicateWithFormat:@"name == 'Bob'"];

In addition to the operators mentioned above, there are also a slew of operators specifically for comparing string attributes, such as BEGINSWITH, CONTAINS, ENDSWITH, LIKE, and MATCHES. The first three are self-explanatory. The LIKE operator will be familiar to anyone who has worked with SQL: It functions identically to == except that it allows the use of two wildcard characters. When using LIKE, the character * works as an unbounded wildcard, so for example, 'w*t' would match 'wet', 'woot', and 'well I'd like to see you again if you permit it'. The ? character, on the other hand, is a bounded, single character wildcard, so 'w?t' would match 'wet' and 'wat' but not 'woot'.

MATCHES is similar to LIKE, except that it allows the use of full regular expressions rather than the more simplistic wildcards supported by LIKE. Regular expressions are beyond the scope of this article, so we won't be discussing MATCHES at all, but I will reiterate my comment from the first Core Data article that taking the time to learn regular expressions is well worth your time as a Cocoa programmer or OS X power user.

String operators have two optional modifiers (c and d) that can be specified in square brackets immediately following the operator. These can be used to specify (respectively) case sensitivity and diacritical sensitivity. By default, string comparisons in Core Data are both case-insensitive and diacritical-insensitive, meaning that if you create a predicate 'name CONTAINS bob', you would get back data instances where the attribute name equals 'Bob', 'bob', 'BOB', or even 'BOB'. Here is an example of creating a predicate string that is case and diacritical sensitive or, in other words, here's how you get 'Bob' without getting his German cousin 'BOB' or his Swedish cousin 'Bob':

NSPredicate *pred = [NSPredicate predicateWithFormat:
   @"name LIKE[cd] %@", @"Bob"];

There are a few more operators available to you, but the ones I've mentioned will make up the vast, vast majority of what you'll use; you can feel free to investigate the others in Apple's Predicate documentation.

There's one more difference between the format strings used by NSString and those used by NSPredicate that needs to be mentioned: NSPredicate supports only two substitution variables, one of which is not supported by NSString. Both NSString and NSPredicate allow the %@ variable for substituting Objective-C objects into the string. NSPredicate does not support the fprint-style format variables such as %d, %f, or %s. It does however, add a new substitution variable not used by NSString: %K.

The %K substitution variable is used for key paths and attribute names. If the name of the attribute you want to compare might change, you cannot use %@ to do so. So, for example, this won't work:

NSPredicate *pred = [NSPredicate predicateWithFormat:
   @"%@ == %@", @"name", @"Joe"];

The reason that this doesn't work is that NSPredicate recognizes the %@ operator only for substituting values not for substituting keys. When you are specifying an attribute name or key path, you have to use %K instead of %@. To correct that previous code example, we simply substitute %K for the first %@ like so:

NSPredicate *pred = [NSPredicate predicateWithFormat:
   @"%K == %@", @"name", @"Joe"];

Fetching with Predicates

Now that you've been introduced to predicates, we're ready to write the rest of our Core Data utility functions. First, let's write a utility method for a very common fetch: fetching all entities where one particular attribute has a particular value. Although NSPredicate allows very complex queries, you're likely to find that the bulk of the queries you actually use in your applications are relatively simple, and this method will make life easier in those situations.

MTCDUtilities.m - objects objectsForEntityNamed:matchingKey:andValue:inContext

+(NSArray *) objectsForEntityNamed:(NSString *)name 
   matchingKey:(NSString *)key 
   andValue:(id)value 
   inContext:(NSManagedObjectContext *)context
{
   // Since NSString and NSPredicate use different format strings,
   // we use a two-step process to create our format string here

   NSString *predString = [NSString stringWithFormat:
      @"%@ == %%@", key];
   NSPredicate *pred = [NSPredicate 
      predicateWithFormat:predString, value];

   // We still need an entity description, of course

   NSEntityDescription *entity = [NSEntityDescription 
      entityForName:name inManagedObjectContext:context];

   // And, of course, a fetch request. This time we give it both the entity
   // description and the predicate we've just created.

   NSFetchRequest *req = [[NSFetchRequest alloc] init];
   [req setEntity:entity];	
   [req setPredicate:pred];

   // We declare an NSError and handle errors by raising an exception,
   // just like in the previous method
	
NSError *error = nil;
   NSArray *array = [context executeFetchRequest:req
      error:&error];    
   if (array == nil)
   {
      NSException *exception = [NSException 
         exceptionWithName:MTCoreDataExeption 
         reason:[error localizedDescription] 
         userInfo:nil];
      [exception raise];
   }

   // Now, release the fetch request and return the array

   [req release];
   return array;
}

Very handy. Now, we have a convenience method for retrieving object instances matching a single key/value pair, like so:

NSArray *results = [MTCDUtilities objectsForEntityNamed:
   @"person" matchingKey:@"lastName" 
   andValue:@"Smith" 
   inContext:context];

More importantly, you now know the basics of creating a fetch request using a predicate, so should be able to craft more complex fetch requests for situations where this method is inadequate.

Now, in practice, you'll likely find yourself using the method above a lot of times with unique identifiers. In those situations, you know you'll only retrieve a single object, but yet you'll still get back an array. For situations where you know there will only be a single matching object, perhaps another convenience method is in order.

MTCDUtilities.m - objectForEntityNamed:matchingKey:andValue:inContext:

This method returns a single object matching a single key/value pair. If more than one object is actually returned, an exception is thrown.

+(NSManagedObject *)objectForEntityNamed:(NSString *)name 
   matchingKey:(NSString *)key 
   andValue:(id)value 
   inContext:(NSManagedObjectContext *)context
{

   // We call the previous method, then return the object at index 0. We 
   // declare no exception handler, so an exception encountered in this call
   // will stop execution of this method and throw up to the code 
   // from where it was called.

   NSArray *array = [MTCDUtilities 
      objectsForEntityNamed:name 
      matchingKey:key andValue:value inContext:context];

   // If there are more than one objects in the array, throw an exception

   if ([array count] > 1)
   {
      NSException *exception = [NSException 
         exceptionWithName:MTTooManyEntitiesReturnedException 
         reason:@"Too many instances retrieved for criteria" 
         userInfo:nil];	
      [exception raise];
   }
   // If there are no objects, just return nil

if ([array count] == 0)
      return nil;
		
   // Return the object at index 0
	
return (NSManagedObject *)[array objectAtIndex:0];
}

Now you'll be able to pull back a specific item with aplomb:

NSManagedObject *item = [MTCDUtilities 
   objectsForEntityNamed:@"book" matchingKey:@"id" 
   andValue:[NSNumber numberWithInt:192] inContext:context];

Notice that I passed an NSNumber instead of a string? That's not only acceptable, it's the correct thing to do when the attribute you're using is a numeric value, just as you would pass an NSDate if you were comparing a date attribute.

Compounding Predicates

But wait! There's more! If you order in the next ten minutes, I'll throw in this free set of Ginsu(TM) knives. Okay, not really, but I will throw in one more handy method. You won't always be able to get away with using queries based on a single key-value pair. Sometimes you'll need, for example, to pull back a person based on a first AND a last name, or pull back all entities of one type OR another type. These situations are accomplished with a specialized subclass of NSPredicate called NSCompoundPredicate.

Given any two or more existing NSPredicates, you can create a compound predicate using the logical operators AND, OR, or NOT. You simply create an array with all the predicates you want to join, and pass them into one of NSCompoundPredicate's convenience class methods, like so:

NSArray *preds = [NSArray arrayWithObjects:
   pred1,pred2, nil];
NSPredicate *notPred = [NSCompoundPredicate 
   notPredicateWithSubpredicates:preds];
NSPredicate *andPred = [NSCompoundPredicate 
   andPredicateWithSubpredicates:preds];
NSPredicate *orPred = [NSCompoundPredicate 
   orPredicateWithSubpredicates:preds];

You can even compound existing compound predicates, which we'll do in this last method.

MTCDUtilities.m - objectsForEntityNamed:matchingKeysAndValues:usingOR:inContext

Finds all instances of a given entity based on an NSDictionary of key/value pairs. The key-value pairs will be turned into equality predicates and compounded using either OR or AND depending on the value of useOR.

+(NSArray *)objectsForEntityNamed:(NSString *)name 
   matchingKeysAndValues:(NSDictionary *)keyValues 
   usingOR:(BOOL)useOR 
   inContext:(NSManagedObjectContext *)context
{
   // We'll retrieve an enumerator of all the keys in the dictionary
   NSEnumerator *e = [keyValues keyEnumerator];

   // Declare the predicate outside of the enumerator loop
	
NSPredicate *pred = nil;

   // Declare a string to hold the current key while looping

    NSString *key;
    
   while (key = [e nextObject]) 
   {
      // Declare a format string for creating the current subpredicate

      NSString *predString = 
         [NSString stringWithFormat:@"%@ == %%@",  key];

      // First time through, pred is nil and shouldn't be compounded with anything

      if (pred == nil)
         pred = [NSPredicate predicateWithFormat:predString, 
            [keyValues objectForKey:key]];
      else
      {

         // if pred is not nil, then create a compound based on the new
         // subpredicate tempPred and the existing predicate pred

         NSPredicate *tempPred = [NSPredicate 
            predicateWithFormat:predString, 
            [keyValues objectForKey:key]];
         NSArray *array = [NSArray arrayWithObjects:tempPred, 
            pred, nil];
            if (useOR)
            pred = [NSCompoundPredicate 
               orPredicateWithSubpredicates:array];
      else
            pred = [NSCompoundPredicate a
               ndPredicateWithSubpredicates:array]; 
      }
   }

   // Everything from here down should look familiar.

   NSEntityDescription *entity = [NSEntityDescription 
      entityForName:name inManagedObjectContext:context];
  NSFetchRequest *req = [[NSFetchRequest alloc] init];
   [req setEntity:entity];	
   [req setPredicate:pred];
   NSError *error = nil;
   NSArray *array = [context executeFetchRequest:req 
      error:&error];    
   if (array == nil)
   {
      NSException *exception = [NSException 
         exceptionWithName:MTCoreDataExeption 
         reason:[error localizedDescription] 
         userInfo:nil];
      [exception raise];
   }
   [req release];
   return array;
}

To use this last method, simply pack a dictionary with the attributes as the keys and the values to compare them to as the values, like so:

   NSDictionary *dict = [NSDictionary 
      dictionaryWithObjectsAndKeys:@"BookCo", @"publisher", 
      [NSNumber numberWithInt:482769], @"salesRank", nil];
   NSArray *array = [MTCDUtilities objectsForEntityNamed:
      @"Book" matchingKeysAndValues:dict usingOR:YES 
      inContext:[self managedObjectContext]];

Conclusion

This article gives you the last missing major building block for creating Core Data applications. Between this article and its two predecessors, you should now feel comfortable creating a Core Data data model, hooking it into your interface, interacting with it programmatically, and finding data within your application's context.

Core Data may seem a little intimidating at first, especially if you've never worked with an object-relational mapping tool such as EOF or Cayenne before. Hopefully these three articles have given you enough information to realize that Core Data really isn't scary at all, but that it can give you a frightening increases in your productivity.


Jeff LaMarche wrote his first line of code in Applesoft Basic on a Bell & Howell Apple //e in 1980 and he's owned at least one Apple computer at all times since. Though he currently makes his living consulting in the Mac-unfriendly world of "Enterprise" software, his Macs remain his first and greatest computer love. You can reach him at jeff_lamarche@mac.com.

 
AAPL
$119.00
Apple Inc.
+1.40
MSFT
$47.75
Microsoft Corpora
+0.28
GOOG
$540.37
Google Inc.
-0.71

MacTech Search:
Community Search:

Software Updates via MacUpdate

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

Latest Forum Discussions

See All

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

Price Scanner via MacPrices.net

Apple Store Black Friday sale for 2014: $100...
BLACK FRIDAY The Apple Store has posted their Black Friday deals for 2014. Receive a $100 PRODUCT(RED) branded iTunes gift card with the purchase of select Macs, $50 with iPads, and $25 with iPods,... Read more
Black Friday: 15% off iTunes Gift Cards
Staples is offering 15% off $50 and $100 iTunes Gift Cards on their online store as part of their Black Friday sale. Click here for more information. Shipping is free. Best Buy is offering $100... Read more
BEVL Releases Dock Tailored for iPhone 6 and...
Seattle based BEVL has released their first product: an iPhone dock that is divergent in build quality, rock-solid function and visual simplicity to complement the iPhone. BEVL is now accepting... Read more
Black Friday: $150 off 13-inch Retina MacBook...
 Best Buy has 13-inch 2.6GHz Retina MacBook Pros on sale for $150 off MSRP on their online store as part of their Black Friday sale. Choose free shipping or free local store pickup (if available).... Read more
Black Friday: $300 off 15-inch Retina MacBook...
 B&H Photo has the new 2014 15″ Retina MacBook Pros on sale for $300 off MSRP as part of their Black Friday sale. Shipping is free, and B&H charges NY sales tax only: - 15″ 2.2GHz Retina... Read more
Black Friday: Up to $140 off MacBook Airs, fr...
 B&H Photo has 2014 MacBook Airs on sale for up to $140 off MSRP as part of their Black Friday sale. Shipping is free, and B&H charges NY sales tax only: - 11″ 128GB MacBook Air: $799 $100... Read more
Black Friday: 13-inch 2.5GHz MacBook Pro on s...
 Best Buy has the 13″ 2.5GHz MacBook Pro on sale for $899.99 on their online store as part of their Black Friday sale. Choose free shipping or free instant local store pickup (if available). Their... Read more
Black Friday: 21-inch 1.4GHz iMac on sale for...
 Best Buy has the 21″ 1.4GHz iMac on sale for $899.99 on their online store as part of their Black Friday sale. Their price is $200 off MSRP. Choose free shipping or free local store pick up. Price... Read more
Black Friday iPad Air 2 sale prices, $100 off...
 Best Buy has iPad Air 2s on sale for $100 off MSRP on their online store for Black Friday. Choose free shipping or free local store pickup (if available). Sale prices available for online orders... Read more
2014 1.4GHz Mac mini on sale for $449, save $...
 B&H Photo has the new 1.4GHz Mac mini on sale for $449.99 including free shipping plus NY tax only. Their price is $50 off MSRP, and it’s the lowest price available for this new model. Adorama... Read more

Jobs Board

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