Sunday, January 24, 2010

Core Data

I was fortunate to attend the iPhone Tech Talk conference, hosted by Apple Inc. in San Jose last October, 2009. One of the sessions I attended was on Core Data. At a very high level, Core Data is an Apple™ software framework for storing data on the computer - namely, the iPhone and the Mac. Please note that I don't plan on covering database design theory in this post so I'm assuming a familiarity with it.

Core data is an Object graph management and persistence framework. Core data maintains a graph of objects (otherwise known as a managed object graph or managed object context). References to managed objects are stored in the object graph. Core Data serializes these objects and persists them to a data store. Core data can use several different underlying data stores - xml, binary, sqlite. The managed object context is a key concept in Core Data, as it provides a proxy between the application objects and the underlying data store(s). The persistent store coordinator is responsible for managing the relationship between the managed object context and the persistent store(s). A managed object context wraps a collection of managed objects and sits on top of a persistent store coordinator which houses one or more persistent stores. A managed object context is created via an instance of the NSManagedObjectContext class.

It's important to note that the Core Data sqlite file should not be accessed directly from within code, as the schema may change and it's not intended for direct manipulation.

In Summary, a managed object context is a collection of managed objects. Managed objects are instances of the entities that are defined in the data model. A managed object represents a row of data in a table. The managed objects context sits on top of a persistent store coordinator. The managed object context is a proxy between the objects in the application and the underlying data store(s). A persistent object store is represented by a xml, binary, or sqlite file. The persistent store coordinator sits between the managed object context and the persistent store(s). The persistent store coordinator in turn abstracts one or more persistent object stores as a single aggregate. Please note, a persistent store coordinator can only be associated with one managed object context. But a persistent store coordinator can have many persistent object stores. The managed object context talks directly to the persistent store coordinator. The managed object context is a scratchpad of managed objects.
After working with Core Data, it is a worthwhile exercise to open the core data sqlite file and dig through the tables. A quick look through the tables will explain how Apple is handling referential integrity. The Core Data sqlite file is "not meant" to be accessed directly from within code. Do not modify it or attempt to write queries against it. There is no guarantee that the schema will not change. Core Data is meant to abstract implementation dependent details (i.e. the implementation may change upon any future releases).

The following notes were derived from three sources: Apple WWDC 2009, the iPhone Core Data session in San Jose last October, and my experience working with object/relational databases.

Creating a Core Data Stack

  1. Load the Data Model
  2. Create the persistent store coordinator and set the model
  3. Add the data store
  4. Create a managed object context and set the persistent store coordinator

Core Data Model

  1. A Core Data model does not need to be normalized. Define data contracts early on. Don't over tighten the data model - design with usage patterns in mind. Define entities or tables with Xcode's visual editor. (core data objects represent rows in these tables). In other words, a Core Data object (row of data) of type PERSON is an instance of the PERSON entity in the Core Data model. Of course, a person object will have relationships with other objects in the database. A Core Data Managed object context manages these objects, their relationships, and persists them to the underlying data store (via the persistent store coordinator). Core Data is simple to setup. Don't complicate it.
  2. NSManagedObjectContext defines the verbs (CRUD or fetch/insert/delete/save) that act on managed objects (nouns)
  3. NSManagedObjectContext tracks changes to properties in the data model
  4. Core Data fits very well in the MVC architecture.
  5. An NSPersistentStoreCoordinator can have multiple persistent stores(NSPersistentStore) and multiple Managed Object Contexts can use a single NSPersistentStoreCoordinator
  6. It is possible to delete the sqlite store file if the stored data needs to be deleted. This file is stored in $HOME/Library/Application Support/iPhone Simulator/User/Applications/SOMEAPPLICATIONGUID/Documents
  7. Use Core Data migration or built-in versioning for model changes. easy to use. zero code.

Multithreading

  1. Core data assigns one managed object context per thread; use notifcations for changes made to managed objects within a managed object context.
  2. never pass managed objects between threads, pass object IDs (actually the primary key)
  3. within a thread, create a new Managed object context and then tell the new Managed object context what objects IDs to fetch

Fetching

    Use NSFetchedResultsController (break up fetches into sections) - it is fast, efficient, and easy.
  1. Create an NSFetchedResultsController
  2. Set the fetch request, predicate, and sort descriptors. fetch request provides predicate and sort descriptors). fetch request and predicate are immutable.
  3. the keypath returns the section name (section information is cached)
  4. set yourself as its delegate and implement your tableview methods
  5. Create separate controllers for different data sets

NSFetchRequest

  1. set the entity that you want to fetch against
  2. create NSEntityDescription - provide managed object context and entity you want to work with
  3. set the predicate (if applicable)
  4. Fetch - pass in fetch and error

Batching

  1. setup fetch request (only need some of the objects at a time so setBatchSize:)
  2. set batch size
  3. you get back an NSArray of results

Prefetching

  1. use setRelationshipKeyPathsForPrefetching:

Managed Object Interaction

    NSManagedObject
  1. Use accessors
  2. Objective-C properties
  3. To Many relationships are sets
  4. NSManagedObjects provides Key value coding/observing out of the box
  1. Managed Object Context observes object changes. Leverage change tracking notifcations
  2. Register for Object Level Changes (KVO)
  3. Register for Graph Level Changes (NSNotifications)


Iterate over Custom UITableViews

UITableViews on the iPhone often contain a variable number of cells. The following code snippet should be helpful for those who need to iterate over a variable length table and subsequently read or set data model and cell state.
   //loop through cells in each section and make sure the data model is in sync 
   NSInteger numSections = [self numberOfSectionsInTableView:someTable];
   for (NSInteger s = 0; s < numSections; s++) { 
      NSLog(@"Section %d of %d", s, numSections); 
      NSInteger numRowsInSection = [self tableView:someTable numberOfRowsInSection:s]; 
      for (NSInteger r = 0; r < numRowsInSection; r++) {
         NSLog(@"Row %d of %d", r, numRowsInSection); 
         MyCell *cell = (MyCell *)[someTable cellForRowAtIndexPath: 
                                  [NSIndexPath indexPathForRow:r inSection:s]]; 
         DataModelObject *theObject = (DataModelObject *)[fetchedResultsController objectAtIndexPath:
                                                         [NSIndexPath indexPathForRow:r inSection:s]]; 
         [theObject setValue:[NSNumber numberWithBool:MyCell.someProperty] forKey:@"someEntityKey"]; 
      } 
   }