TweetFollow Us on Twitter

Introduction to Core Data

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

Introduction to Core Data

Using Tiger's New Persistence Framework and Modeling Tool

by Jeff LaMarche

IT Types Finally Get A Room of Our Own

Introduction

This our second article on the new Cocoa technologies available with the release of Tiger. In the last installment, we focused on the new tree-based XML parser, NSXML. This month and in future articles, we'll be delving into Core Data. In order to better understand what Core Data is and where it came from, a bit of history is in order.

Way back in the day, NeXT had a wonderful something called EOF: the Enterprise Objects Framework. EOF was not just a framework , but also a tool (called EOModeler) that allowed you to create your application's data model visually instead of manually creating objective-C classes. Even though your data model was not made by writing code, you could interact with your data as objective-C objects by using key-value coding; the framework handled all the work involved in persisting the data to a SQL database, flat file, or any other data store for which somebody wanted to write an EOAdaptor. You rarely had to write a single line of SQL or file management code in order to make your program work; all that happened for free.

EOF allowed unbelievable developer productivity in both creating and maintaining applications. And due to its object-oriented architecture, it offered limitless flexibility because you could always opt to write your own classes to override or supplement the functionality you got for "free" by using it. While a version of EOF still exists in WebObjects (EOF/WO), it is Java based and not available nor licensed for use in regular Cocoa applications. The objective-C version still exists and is installed with WebObjects, but it's not actively supported and the adaptors have not been kept up to date.

With the release of Tiger and Core Data, EOF has risen from the dead... sort of.

The More Things Change...

Apple's own documentation states that Core Data and EOF/WO share a common heritage. Despite that, they are clearly intended for different purposes. EOF/WO is used almost exclusively for writing database-backed web applications.

Core Data, on the hand, was specifically designed without support for remote databases; it is intended (at least for now) just to provide local data storage for Cocoa applications. Lack of support for remote databases, in the eyes of many developers, is a significant limitation. Despite that, Core Data does what it was designed to do admirably well. In some ways, Core Data is better than EOF. The modeling functionality is much more polished than what EOModeler offered and it's integrated right into XCode, so there's no need to switch back and forth between the IDE and modeller. Core Data's limited storage options also simplify things; the built-in storage options- which include storing data as XML, in binary files, or in an embedded SQLite database- means no more hassles finding the EOAdaptor you need or making sure your users have the correct adaptors (and version) installed on their machines.

Combined with Cocoa Bindings, which were added with Panther, Core Data is nothing short of miraculous in terms of the increase in developer productivity it affords you.

Overview and Terminology

The best way to show how wonderful Core Data is, is to just dive in and use it. Before we do that, however, let's take a second to go over some basic terminology.

Entity - this is the highest level object in a Core Data data model. Entities generally corresponds to the Cocoa classes traditionally used to hold data. They can hold data directly by way of their attributes and by incorporating other entities using relationships and fetched properties. Programmatically, entities are most often represented by the NSManagedObject class, but can also be represented by custom subclasses of NSManagedObject. Entities can be abstract and Core Data supports a rudimentary form of inheritance among entities. This functionality is beyond the scope of this article, and (at least in the days of EOF) was seldom used in practice. In fact, at one time, the use of entity inheritance was discouraged in Apple's official documentation.

Attribute - an attribute is a property of an entity that holds data. Attributes roughly correspond to the instance variables of a traditional data model class excluding collections such as NSArray and instance variables that are themselves also custom data model classes. There are a limited number of attribute types, such as strings, a few types of numbers, dates and a few others. Attributes are represented in code by the class NSAttributeDescription.

Relationship - a relationship is Core Data's primary way of making one entity act as part of another entity. Relationships can be one-to-one--which is analogous to declaring an instance of one data model class as a variable of another class--or they can be one-to-many, which parallels the use of an NSArray instance variable. You use the NSRelationshipDescription to interact with relationships in your code.

Fetched Property - fetched properties are similar to relationships in that Fetched Properties allow objects to behave as if they are part of another object. The key difference is that while relationships tie specific entity instances to another entity, Fetched Properties incorporate entities based on specified criteria, very much like the rule-based "smart" functionality that has popped up in so many places lately: smart playlists in iTunes, smart mailboxes in Mail, smart photo albums in iPhoto, etc. NSFetchedPropertyDescription is the Cocoa class used to represent fetched properties.

The term "property" is used generically to refer to all items that can be contained with an entity: fetched properties, relationships, and attributes. Entities and the three types of properties make up the actual Core Data data model. Here are a few more Core Data terms and classes with which you should be familiar before we get our feet wet:

Context - the "context" is the virtual space in which data is stored and corresponds to the physical data store on your local hard drive. For a Core Data application, you'll typically only have one context, though you could have more. Within a Core Data Document-Based application, you'll typically have a separate context for each open document. In Core Data, the context is represented by the NSManagedObjectContext class.

Predicates - the easiest way to think of a predicate is to think of it as a "rule", in the sense of smart playlists. An example of a predicate, rendered in English, would be "name begins with 'A'" or "price is less than $20". The NSPredicate class and its two subclasses, NSCompoundPredicate and NSComparisonPredicate represent predicates in code. Predicates can be compounded using the logical operators and, or, and not.

Fetch Request - this is how you retrieve entities from the context. Fetch requests can be unqualified, in which case they will return all instances of a specific entity, or they can be qualified using predicates. Fetch Requests are represented by the NSFetchRequest class. You can also retrieve data from the context by using NSArrayControllers, which we'll do later in this article.

Creating the Data Model

You may remember in the NSXML article, we created a class called MTBook to store the data we retrieved from Amazon's XML-based web service. Today, we're going to recreate that data model without writing a single line of code. Open up XCode and create a new project using the Core Data Application template and call it MTCoreData.

If you open up the Models folder in your project, you will see that there is already a model file called MTCoreData_DataModel.xcdatamodel. Click it, and it should bring up the Core Data model editor (if not, click the Editor icon in your toolbar). There are four sections to the editor (figure 1).


Figure 1. Blank Core Data Editor

This is the list of entities in your data model. The plus sign in the lower left, next to the scroll bar, will add an entity, which can be edited in section 3. The minus sign will allow you to delete entities, and the triangle next to the minus sign will allow you to change the view from a flat list to a hierarchical display.

This is the list of all the properties for the entity currently selected in section 1. The plus sign in the lower left allows you to add a new relationship, attribute, or fetched property. The minus sign will delete the currently selected property, and the triangle next to it will present a drop down that allows you to filter the property display based on type.

This is where the selected entity or property can be edited. If an entity is selected, but no property is, then this area will display the editable traits of the entity. If both an entity and a property are selected, then the editable traits of the property will be displayed here.

This is the object graph: a visual display of the data model. You can also edit many of the entity and property traits right in the object graph display if you want.

Go ahead and click on the plus sign in the lower left of section 1. This will add and select a new entity called Entity. The naming convention for entities is that they start with a capital, followed by lowercase letters except you capitalize the first letter of any new word (e.g. FaxMachine). Entities should be named as singular, not plural (not FaxMachines). Go ahead and change the name of the entity to Book. We don't need to call it MTBook because entity names only have to be unique within the data model, so there's no chance for a name conflict as there is with objective-C classes. Leave the class as NSManagedObject; we'll get into custom classes in a future article, but for now, leave it at the default value. We also won't be specifying a parent or making it abstract, so we are done with editing this entity.

Now, click on the plus sign at the lower left of the properties section and add an attribute called isbn. The naming convention for properties is the same as for objective-C instance variables: lowercase letters except where a new word starts (e.g. faxMachine). Unclick the optional checkbox - we want Core Data to require this field, but leave transient unchecked (transient properties do not get saved to the data store; this can be used for temporary or calculated properties, but are not commonly used without also creating a custom subclass of NSManagedObject).

Set the attribute's type to String. You'll notice that you get some new fields after changing the type. These allow you to do some validation without writing code. ISBN numbers must be ten characters, so we could set the minimum and maximum lengths both to 10, which would cause Core Data to reject input that is shorter or longer than ten characters. We won't do that, however, because there's another way to validate this input. We can also give the attribute a default value. We won't do that here, but we will for other attributes.

That leaves just one field: Reg. Ex., which stands for "regular expression." If you're not familiar with regular expressions, it's well worth your time as a programmer to learn them; they often allow you to do a lot of work by writing a single, very compact string. They are also the key to using a number of the powerful command line tools that ship with OS X, such as grep, awk, and sed, not to mention the programming language Perl.

The reason we don't need to set a minimum or maximum length is because we can enforce that with a regular expression. ISBN values not only have to be 10 characters long, but the first nine characters must be numbers, and the final character must be a number or the letter X. Sure, we could write a half-dozen lines of code to enforce this, if we were creating a subclass of NSManagedObject, but the Reg. Ex. field gives us a way to do this without writing any code. We can make Core Data validate the entered value by simply typing the value \d{9}[\dXx] into the Reg. Ex. field. A regular expression tutorial is way beyond the scope of this article but, briefly, this string says that a valid input must have nine numeric digits followed by either another digit or an upper or lowercase x.

Add another string attribute called title. Give it a default value of Untitled Book and unclick optional. Also add string attributes called publisher and comment, both of which can be optional and have no default values. You can specify a minimum or maximum length if you want, but I've left them blank.

Core Data has no entity type corresponding to a URL, so we'll also use a string to store the book's URL. Create another string attribute called url, make it optional with no minimum or maximum length.

Next, we need to hold two dates: dateReleased and dateRead, so add two more attributes and give them the type of Date. Don't worry about giving a minimum, maximum or default value.

In MTBook, our variable salesRank was an int. In Core Data, there are a few different options for storing integers, the only difference among them is the amount of memory they use. Since Amazon sales ranks can be very big numbers, create a new entity called salesRank and use an Integer 32 for the type. This can be optional, and we need no maximum or default value. We will put a minimum value of 0, however, since sales ranks have to be positive.

We also stored the cover image in MTBook as an NSData. Core Data does not have an image type, but it does have a type called Binary Data which can hold just about any type of data, much like NSData, so add another attribute called coverImage and assign it a type of Binary Data.

Well, that takes care of everything except authors. In the book information returned from Amazon, a single book can have multiple authors. We can represent this in Core Data by creating another entity and using relationships to link our two entities. Add a new entity called Author, again not abstract and with no parent entity. We only need to track one piece of information about an author--his or her name-- so add a new entity to Author and call it name. Set the type to String and unclick optional. Next, we need to create a relationship between the Author entity we just created and the Book entity, which we can do by adding a relationship to Author and calling it books. Unclick optional and set the destination entity to Book. We will keep this as a "to-one" relationship, which will cause Core Data to create and store a new instance of an Author entity for every author of every book. A more efficient way to do it would be to click the To-Many Relationship checkbox and reuse Author entities when an Author entity already exists, but that can't be done just using Cocoa Bindings and would require some code to enforce. For sake of brevity, implementing the more efficient version is left as an exercise for the reader.

Finally, we need to go back to the Book entity and define its relationship back to the Author entity we just created. Since we need to be able to store multiple authors in a single book, create a new relationship in the Book entity and call it authors. Leave it optional, since there are some books without a known author, and set the destination entity to Author. Set the inverse relationship to books, which tells Core Data that these two relationships are actually the same relationship viewed from different entities (as you do it, watch the object graph and you'll see the two lines connecting the Book and Author entities merge into a single line). This time, we do want to click the To-Many Relationship checkbox, since we need to be able to store more than one author for a book.

Save it. Your data model is done. We haven't written a single line of code, yet everything is now in place to initialize, use, persist, delete, and copy data.

Creating and Binding the Interface

This step, at first, works exactly as it has for years. Double-click MainMenu.nib in XCode, which will open up Interface Builder where you can design your interface. I made mine look like this:


Figure 2. The window layout

In order to implement the Lookup button, we will have to write some code, but surprisingly little. Before we do that, however, we'll use Cocoa Bindings to link the user interface directly to our Core Data context, which will let us add, edit, and delete objects without writing code.

In order to proceed, we do need to add a couple of non-visual items to our nib. These are NSArrayController instances. NSArrayControllers are a handy part of Cocoa Bindings that take care of most of the potential interactions a user or user interface element could have with an array of data. With the release of Core Data, NSArrayControllers can be configured to automatically load their data from the context. We need one controller to manage all the books that have been entered, and one to manage the authors of the currently selected book.

To add an NSArrayController instance to your nib, simply drag the icon that looks like three little green boxes from the Controllers palette (figure 3) to your nib file's main window and name it Books.


Figure 3. NSArrayController icon

Single-click the new array controller, and press ?1 to bring up the array controller's attributes in the inspector palette. Make sure that the mode is set to Entity and Automatically Prepares Content is selected. Type Book into the Entity Name field. This is how Cocoa Bindings knows to populate the array controller from Core Data with all the Book entities.

We also have to tell Cocoa Bindings the appropriate context from which to populate the array controller. We can do this by pressing ?4 and bringing up the array controller's bindings in the inspector. The bottommost entry on the bindings palette, under the Parameters section, is managedObjectContext. Click this to expand it, and click the Bind checkbox. For the Bind To field select MTCoreDataAppDelegate, the application delegate that was automatically created for you.

In the Model Key Path field, type in managedObjectContext, which is the name of an accessor method created automatically for you in MTCoreDataAppDelegate that returns the application's context. Leave the Value Transformer field blank; we'll talk about value transformers in a future article.

That takes care of the NSArrayController that will be used for the books table on the left side of the window. We also need one for the authors table. This one's a tad bit trickier because, unlike the Books array controller, we only want to show those authors that correspond to the currently selected book. Drag another NSArrayController to your nib and rename it Authors.

In the controller's attributes, make sure it's set to Entity with a name of Author and that Automatically Prepares Content is selected. In the array controller's bindings, set the managedObjectContext exactly as you did with the Books array controller.

Now, we'll tell it to only load those authors who are authors of the currently selected book. We do this with the contentSet binding under the Controller Content heading. Click on it to expand it, and click the bind checkbox. Bind this to the Books array controller with a Controller Key value of selection, which should be the default and tells Core Data to pull data from the selected item in the Books array controller. In the Model Key Path field, type authors, which is the name of the relationship in our Book entity that we want to use for populating this array controller.

Now, let's bind up the user interface elements. Double-click the books table down the left side, then double-click again so that the table column, and not the table or scroll view, is selected. Type ?4 to bring up the bindings and click on the value binding to expand it. Bind it to the Books array controller with a Controller Key of arrangedObjects, which binds the column to the objects in the Books array, in the correct sort order if one is specified. Finally, in the Model Key Path field, type in title, which is the name of the attribute we want to display in this column. The only other thing we need to do is to check Continuously Updates Value, which ensures that the data model and user interface are always kept synchronized, not just when you tab out of a field.

Next, double-click the other table on your interface--the authors list--twice, so that the table column is selected and bring up the bindings for it. This time, bind the value parameter to the arrangedObjects key of the Authors array controller and type in name for the key path, which tells it to display the name of the author in this column.

That takes care of the two tables on our interface. The individual fields are just as easy. Since we want to display data about the book currently selected in the left-hand table, we need to bind each field to the selection key of the Books array controller. For every field, Continuously Updates Values should be checked. The only difference in the bindings of the different fields is the Model Key Path field, which should be the attribute name (from the Book entity) that you want displayed in that field. You can also check Validates Immediately if you want the input value to be checked when tabbing out of the field rather than at save time. Here's what the value binding should look like for the isbn field:


Figure 4. Bindings for ISBN field

Once you've bound all the other fields on the interface (make sure the NSImageView is set to enabled), there's just one task remaining before we try it out. See those little buttons with the plus and minus signs? We've already created the ability to edit data in the context. Now, we're going to let these buttons add and delete Book and Author entities to the context, again without writing any code. Control-click the plus button in the lower left of the window and drag to the Books array controller. Connect the button's target to the add: outlet. Repeat with the minus button next to it, but connect the target instead to the remove: outlet. Do the same thing for the two buttons below the authors table, but bind to the Authors array controller instead of the Books array controller.

Let 'Er Rip...

Well, we haven't written any code, but go ahead and compile and run the application. Press the plus button and you'll get a new, untitled book. Edit any of the fields or drop a picture onto the image view, and then quit. When you launch the program again, your data is still there. If a book is selected, and you press the minus button, the book gets deleted. Hit ?Z and it comes back. Look Mom: free undo! Change the title of the book, and the table column on the left instantly reflects the change. Pretty easy, huh?

A Little Spit and Polish

We can do a few quick things to add some professional touches to our application. We can link the Save menu item to the saveAction: outlet of MTCoreDataAppDelegate, and allow the user to save any time in the fashion they're used to, rather than just at when the application quits. Want to give the ability to revert? Still easy, but we do have to add bit of code for that.

Listing 1: MTCoreDataAppDelegate.m

revertAction

Adding these two methods to our application controller and then binding the Revert menu item to the first one allows the user to revert to the last saved version. The first method simply invokes a sheet asking if they really want to revert. The second method is called when the sheet ends, and does a rollback if the user confirmed. Rollback is the SQL term for reverting: The EOF heritage shows here in the choice of method name.

- (IBAction)revertAction:(id)sender
{
	NSBeginAlertSheet(@"Are you sure?",@"No",@"Yes",nil,
	window,self,nil,
	@selector(reverSheetDidEnd:returnCode:contextInfo:),
	nil,@"Are you sure you want to revert?");
}
- (void)reverSheetDidEnd:(NSWindow *)sheet
 	returnCode:(int)returnCode 
	contextInfo:(void *)contextInfo
{
	if (returnCode == NSAlertAlternateReturn)
 		[[self managedObjectContext] rollback];
}

Another quick bit of polish we can do is to make the minus buttons enabled only when a book is highlighted and, therefore, able to be deleted. We can do this by binding its enabled binding parameter to the canRemove controller key of the Books array controller. Likewise with the authors remove button to the Authors array controller.

Finally, one last nicety we'll handle this month is to tell the Books array controller to sort based on the title of the book. This will cause the table on the left hand side to display the titles in alphabetical order, since it's bound to the arrangedObjects outlet of that NSArrayController. Unfortunately, this one takes a bit of code, but not much.

Listing 2: MTCoreDataAppDelegate.m

Sorting the Books array controller

To sort our books table, we need to create a sort descriptor and supply it to our array controller. Create an IBOutlet instance variable in the MTCoreDataAppDelegate.h header file. Make it an NSArrayController and give it a name of books. In interface builder, bind the Books array controller to this outlet (you may have to drag MTCoreDataAppDelegate.h over to Interface Builder to make sure it knows about your changes). The best place to specify our sort descriptor is when our application is finished launching, so we'll use the handy notification method applicationDidFinishLaunching: that we get automatically by virtue of being the NSApplication delegate.

- (void)applicationDidFinishLaunching:
	(NSNotification *)aNotification
{
	// This sort descriptor says to sort alphabetically, in ascending order (A then B then C)
	// based on the property called title.
	NSSortDescriptor* sort = [[NSSortDescriptor alloc] 
		initWithKey:@"title" ascending:YES];
	// Tell our array controller to use our sort descriptor
	[books setSortDescriptors:
		[NSArray arrayWithObject: sort]];
	[sort release];  
}
// The Books array controller also has to be told to re-sort when something that affects
// the sort order changes. In our case, the title field is the only one that can affect the
// sort order of the book table, so we'll bind it to this method in Interface Builder.
// Doing so tells the array controller to re-sort based on the sort descriptor we gave it 
// any time the title value is changed on the interface earlier
- (IBAction)rearrangeBooks:(id)sender 
{
    [books rearrangeObjects];
}

One Last Thing

MTCoreDataAppDelegate, the application delegate that was created automatically for you has an accessor method, also created automatically, that it's worth taking a look at because it's the key to changing the Core Data storage options. By default, Core Data uses an XML file. You can change this to use a binary file or an embedded SQLite database, by making a minor tweak to the managedObjectContext method. Look at the items in bold in the following excerpt from mangedObjectContext; the first is the filename that will be used for the persistence store, the second tells Core Data how to store the data.

Listing 3: MTCoreDataAppDelegate.m

Changing Core Data Storage Options

url = [NSURL fileURLWithPath: [applicationSupportFolder 
	stringByAppendingPathComponent: @"MTCoreData.xml"]];
coordinator = [[NSPersistentStoreCoordinator alloc]
	initWithManagedObjectModel: [self managedObjectModel]];
if ([coordinator addPersistentStoreWithType:NSXMLStoreType 
	configuration:nil URL:url options:nil error:&error])

By changing the value NSXMLStoreType to one of the other available options, you change how Core Data will persist your data. Changing the value to NSBinaryStoreType will cause Core Data to store the data as a binary file. Storing it in NSSQLiteStoreType will cause Core Data to create an embedded SQLite database. There is actually a fourth possible option here, but it's one that doesn't make much sense to use: NSInMemoryStoreType. Specifying the last option will cause Core Data to discard all your data at quit time, so won't generally be recommended by people who like you.

Although not strictly required, it is good form to change the filename used to create url so that a different file extension is used if you change the store type; having a binary or SQLite file with a .xml ending is likely to cause confusion. Feel free to use any extension that seems appropriate for your situation. Personally, I use .sqlite3 when using NSSQLiteStoreType and no extension when using NSBinaryStoreType, but use whatever tickles your fancy.

Conclusion

At this point, we've written around thirty lines of code--and that's even counting lines with just a bracket--and we have a full-fledged application capable of saving text, dates, and images and displaying them in an ordered table view. Our application validates field values, allows undo and revert and generally gives the user a good chunk of the functionality they expect from a finished application. In our next article, we'll re-implement the Amazon ISBN lookup to show how we add, edit, and delete data programmatically, then we'll dive into some more advanced functionality requiring us to subclass NSManagedObject.

This article should have given you enough of a glimpse of Core Data's possibilities to understand why many developers are salivating over it. Core Data can give an incredible boost to your productivity and allow you to create more robust, more easily maintained applications faster than previously possible and, frankly, it's hard to hate that.


Jeff LaMarche is a long time Mac user and programmer who still hasn't figured out what he wants to do when he grows up, which is okay, because he probably won't. You can reach him at jeff_lamarche@mac.com.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

MacFamilyTree 8.2.7 - Create and explore...
MacFamilyTree gives genealogy a facelift: modern, interactive, convenient and fast. Explore your family tree and your family history in a way generations of chroniclers before you would have loved.... Read more
WhatsApp 0.2.8000 - Desktop client for W...
WhatsApp is the desktop client for WhatsApp Messenger, a cross-platform mobile messaging app which allows you to exchange messages without having to pay for SMS. WhatsApp Messenger is available for... Read more
TotalFinder 1.10.7 - Adds tabs, hotkeys,...
TotalFinder is a universally acclaimed navigational companion for your Mac. Enhance your Mac's Finder with features so smart and convenient, you won't believe you ever lived without them. Features... Read more
Box Sync 4.0.7886 - Online synchronizati...
Box Sync gives you a hard-drive in the Cloud for online storage. Note: You must first sign up to use Box. What if the files you need are on your laptop -- but you're on the road with your iPhone? No... Read more
Espresso 5.1 - Powerful HTML, XML, CSS,...
Note from the developer: For the new Espresso, we changed our versioning and licensing approach with more consistent pricing and a simpler development timeline: "X+1". Each new update would increase... Read more
VueScan 9.6.04 - Scanner software with a...
VueScan is a scanning program that works with most high-quality flatbed and film scanners to produce scans that have excellent color fidelity and color balance. VueScan is easy to use, and has... Read more
Slack 3.0.5 - Collaborative communicatio...
Slack is a collaborative communication app that simplifies real-time messaging, archiving, and search for modern working teams. Version 3.0.5: Bug Fixes: An important security update. Security... Read more
VirtualBox 5.2.6 - x86 virtualization so...
VirtualBox is a family of powerful x86 virtualization products for enterprise as well as home use. Not only is VirtualBox an extremely feature rich, high performance product for enterprise customers... Read more
Vivaldi 1.13.1008.40 - An advanced brows...
Vivaldi is a browser for our friends. In 1994, two programmers started working on a web browser. Our idea was to make a really fast browser, capable of running on limited hardware, keeping in mind... Read more
WhatRoute 2.1.1 - Geographically trace o...
WhatRoute is designed to find the names of all the routers an IP packet passes through on its way from your Mac to a destination host. It also measures the round-trip time from your Mac to the router... Read more

Latest Forum Discussions

See All

JYDGE (Games)
JYDGE 1.0.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0.0 (iTunes) Description: Build your JYDGE. Enter Edenbyrg. Get out alive. JYDGE is a lawful but awful roguehate top-down shooter where you get to build your... | Read more »
Tako Bubble guide - Tips and Tricks to S...
Tako Bubble is a pretty simple and fun puzzler, but the game can get downright devious with its puzzle design. If you insist on not paying for the game and want to manage your lives appropriately, check out these tips so you can avoid getting... | Read more »
Everything about Hero Academy 2 - The co...
It's fair to say we've spent a good deal of time on Hero Academy 2. So much so, that we think we're probably in a really good place to give you some advice about how to get the most out of the game. And in this guide, that's exactly what you're... | Read more »
Everything about Hero Academy 2: Part 3...
In the third part of our Hero Academy 2 guide we're going to take a look at the different modes you can play in the game. We'll explain what you need to do in each of them, and tell you why it's important that you do. [Read more] | Read more »
Everything about Hero Academy 2: Part 2...
In this second part of our guide to Hero Academy 2, we're going to have a look at the different card types that you're going to be using in the game. We'll split them up into different sections too, to make sure you're getting the most information... | Read more »
Everything about Hero Academy 2: Part 1...
So you've started playing Hero Academy 2, and you're feeling a little bit lost. Don't worry, we've got your back. So we've come up with a series of guides that are going to help you get to grips with everything that's going on in the game. [Read... | Read more »
What mobile gaming can learn from the Ni...
While Nintendo might not have had things all its own way since it began developing for mobile, one thing it has got right is the release of the Switch. After the disappointment of the WiiU, which I still can't really explain, the Switch felt a... | Read more »
Programmer of Sonic The Hedgehog launche...
Japanese programmer Yuji Naka is best known for leading the team that created the original Sonic The Hedgehog. He’s moved on from the speedy blue hero since then, launching his own company based in Tokyo – Prope Games. Legend of Coin is the... | Read more »
Why doesn't mobile gaming have its...
The Overwatch League is a pretty big deal. It's an attempt to really push eSports into the mainstream, by turning them into, well, regular sports. But slightly less sweaty. It's a lavish affair with teams from all around the world, and more... | Read more »
Give Webzen’s new billiard game PoolTime...
Best known for producing hugely popular MMO titles, South Korean publisher Webzen is now taking aim at a different genre altogether. PoolTime is a realistic eight ball pool simulator, allowing you to compete in real-time matches against players... | Read more »

Price Scanner via MacPrices.net

9.7-inch 2017 WiFi iPads on sale starting at...
B&H Photo has 9.7″ 2017 WiFi #Apple #iPads on sale for $30 off MSRP for a limited time. Shipping is free, and pay sales tax in NY & NJ only: – 32GB iPad WiFi: $299, $30 off – 128GB iPad WiFi... Read more
Wednesday deal: 13″ MacBook Pros for $100-$15...
B&H Photo has 13″ #Apple #MacBook Pros on sale for up to $100-$150 off MSRP. Shipping is free, and B&H charges sales tax for NY & NJ residents only: – 13-inch 2.3GHz/128GB Space Gray... Read more
Apple now offering Certified Refurbished 2017...
Apple has Certified Refurbished 9.7″ WiFi iPads available for $50-$80 off the cost of new models. An Apple one-year warranty is included with each iPad, and shipping is free: – 9″ 32GB WiFi iPad: $... Read more
10″ iPad Pros on sale for $50-$75 off MSRP, n...
B&H Photo has 10″ and #Apple #iPad Pros on sale for up to $75 off MSRP. Shipping is free, and B&H charges sales tax in NY & NJ only. Note that some sale prices are restricted to certain... Read more
Apple refurbished Mac minis available startin...
Apple has restocked Certified Refurbished Mac minis starting at $419. Apple’s one-year warranty is included with each mini, and shipping is free: – 1.4GHz Mac mini: $419 $80 off MSRP – 2.6GHz Mac... Read more
Amazon offers Silver 13″ Apple MacBook Pros f...
Amazon has new Silver 2017 13″ #Apple #MacBook Pros on sale today for up to $150 off MSRP, each including free shipping: – 13″ 2.3GHz/128GB Silver MacBook Pro (MPXR2LL/A): $1199.99 $100 off MSRP – 13... Read more
Sale: 12″ 1.3GHz MacBooks on sale for $1499,...
B&H Photo has Space Gray and Rose Gold 12″ 1.3GHz #Apple MacBooks on sale for $100 off MSRP. Shipping is free, and B&H charges sales tax for NY & NJ residents only: – 12″ 1.3GHz Space... Read more
Apple offers Certified Refurbished 2017 iMacs...
Apple has a full line of Certified Refurbished iMacs available for up to $350 off original MSRP. Apple’s one-year warranty is standard, and shipping is free. The following models are available: – 27... Read more
13″ MacBook Airs on sale for $120-$100 off MS...
B&H Photo has 2017 13″ 128GB MacBook Airs on sale for $120 off MSRP. Shipping is free, and B&H charges sales tax for NY & NJ residents only: – 13″ 1.8GHz/128GB MacBook Air (MQD32LL/A): $... Read more
15″ Touch Bar MacBook Pros on sale for up to...
Adorama has Space Gray 15″ MacBook Pros on sale for $200 off MSRP. Shipping is free, and Adorama charges sales tax in NJ and NY only: – 15″ 2.8GHz MacBook Pro Space Gray (MPTR2LL/A): $2199, $200 off... Read more

Jobs Board

*Apple* Solutions Consultant - Apple (United...
# Apple Solutions Consultant Job Number: 113384559 Brandon, Florida, United States Posted: 10-Jan-2018 Weekly Hours: 40.00 **Job Summary** Are you passionate about Read more
Art Director, *Apple* Music + Beats1 Market...
# Art Director, Apple Music + Beats1 Marketing Design Job Number: 113258081 Santa Clara Valley, California, United States Posted: 05-Jan-2018 Weekly Hours: 40.00 Read more
*Apple* Pay & Wallet Engineering Manager...
# Apple Pay & Wallet Engineering Manager, Apple Watch Job Number: 83769531 Santa Clara Valley, California, United States Posted: 06-Nov-2017 Weekly Hours: 40.00 Read more
UI Tools and Automation Engineer, *Apple* M...
# UI Tools and Automation Engineer, Apple Media Products Job Number: 113136387 Santa Clara Valley, California, United States Posted: 11-Jan-2018 Weekly Hours: 40.00 Read more
Senior Product Architect, *Apple* Pay - App...
# Senior Product Architect, Apple Pay Job Number: 58046427 Santa Clara Valley, California, United States Posted: 04-Jan-2018 Weekly Hours: **Job Summary** Apple , Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.