Core Data Classes
The very basic functionality of Core Data is to store objects. Each object should be defined presenting the entities to be persisted, and the relationships between them.
Core Data provide a set of classes to deal with this whole “model definition” issue.
Model
* `NSManagedObjectModel`: A model is where we describe a collection of entities. Is like an *schema*
* `NSEntityDescription`: Each of the entities described in the model. The entity description contains the name, and the managed object class name.
* `NSPropertyDescription`: This is an abstract class. `NSAttributeDescription`, `NSRelationshipDescription` and `NSFetchedPropertyDescription` are the concrete subclasses used to describe the composition of each entity.
For instance, in the almost canonical library example, the model might be composed for a set of entities, one for Books, one for Authors, one for Lenders, etc. The lender entity should have at least the lender’s name, and perhaps some address, these two are NSAttributeDescription. The book entity should contain a name NSAttributeDescription, an author NSRelationshipDescription, and a checkout property to know when the has been checkout. The author entity should be composed by the author’s name, and the books NSRelationshipDescription. And perhaps the lender should be completed with a NSFetchedPropertyDescription of books available to lend.
Attributes are scalar, and strong typed. The attributes types are defined in NSAttributeDescriptions.h
| Name | Name in Xcode Editor | Cocoa Instantiation | Description |
|---|---|---|---|
NSInteger16AttributeType |
Integer 16 | NSNumber |
16-bit integer |
NSInteger32AttributeType |
Integer 32 | NSNumber |
32-bit integer |
NSInteger64AttributeType |
Integer 64 | NSNumber |
64-bit integer |
NSDecimalAttributeType |
Decimal | NSDecimalNumber |
base-10 object wrapper |
NSDoubleAttributeType |
Double | NSNumber |
double object wrapper |
NSFloatAttributeType |
Float | NSNumber |
float object wrapper |
NSStringAttributeType |
String | NSString |
string of characters |
NSBooleanAttributeType |
Boolean | BOOL |
YES / NO |
NSDateAttributeType |
Date | NSDate |
Date object |
NSBinaryDataAttributeType |
Binary | NSData |
Raw Data |
NSTransformableAttributeType |
Transformable | Non Standard Type | Non Standard Type |
If your attribute is of NSTransformableAttributeType, the attributeValueClassName must be set or attribute value class must implement NSCopying.
Knowing how models are represented as objects is important especially if you want to create custom data stores, or even generate models dynamically at runtime.
Store
The NSPersistentStoreCoordinator will create and bind the, ehem, persistent store, and then we can create the NSManagedObjectContext. The persistent store coordinator acts as a mediator between the managed object and the actual persistent stores. This context is our way to interact with the graph, we are going to query the store, and add new items to it, through the NSManagedObjectContext.
The NSPersistenStoreCoordinator is initialized using the NSManagedObjectModel. Once the coordinator is loaded, it can register all the persistent stores.
Cocoa offers four types of persistent stores by default.
| Name | Description | Available in iOS |
|---|---|---|
NSSQLiteStoreType |
SQLite backed database | Yes |
NSBinaryStoreType |
Binary file | Yes |
NSInMemoryStoreType |
Stored only in memory, not in the file system | Yes |
NSXMLStoreType |
XML formatted file | No |
Most of the time, if not always, a typical user will choose NSSQLiteStoreType, thou NSInMemoryStoreType might be excellent for a memory cache as well.
Context
Once the persistent store coordinator is created, we can initialize an instance of NSManagedObjectContext to coordinate the creation, edition or deletion of managed objects. Many ask why we need a managed context, why not save everything to the store. The reason is simple, performance. It makes no sense to hit twice the store to retrieve the same information, or to bloat the memory with data that we don’t need. The context coordinate the many queries, and will try to balance the load. The context will not only controls the access to the store, but will provide a undo / redo system, very similar to the one used in the UI.
Managed objects supports key-value coding. Perhaps the simplest way to access using valueForKey: or setValue:ForKey:. We can say that the subclasses of NSManagedObject are the ones are the data that we describe using the NSEntityDescription.
Managed objects also supports key-value observing. This is not a pattern exclusive to Core Data, but is extremely useful. Instead f querying an object to know if the value of some property has been changed, we register as and observer, and hear either willChangeValueForKey: or didChangeValueForKey:.
[someManagedObjectInstance addObserver:observerObject
forKeyPath:@"propertyName"
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
context:nil]
The options parameter specifies the information that is provided to the observer when a change notification is sent. Using the option NSKeyValueObservingOptionOld specifies that the original object value is provided to the observer as an entry in the change dictionary. Specifying the NSKeyValueObservingOptionNew option provides the new value as an entry in the change dictionary. To receive both values, you would bitwise OR the option constants.
In this case, context is not an instance of NSManagedObjectContext, but a pointer is a C pointer or an object reference. The context pointer can be used as a unique identifier to determine the change that is being observed, or to provide some other data to the observer. The context pointer is not retained, and it is the responsibility of the application to ensure that it is not released before the observing object is removed as an observer. The observing object is not retained either.
To receive notifications, the observerObject has to implement:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context;
Requests
To retrieve information from the context, we use NSFetchRequest instances. Fetch requests are composed by to elements, an NSPredicate and an NSSortDescriptor. These two classes (that belongs to foundation, and are used, for instance, to filter arrays), specify the constrains we want to impose in the query. It is not mandatory to use either of them. The query is not filtered if you avoid the NSPredicate and they are not sorted if you leave out the NSSortDescriptor.
NSMutableArray *predicates = [NSMutableArray array];
[predicates addObject:[NSPredicate predicateWithFormat:@"someProperty >= %@", lBoundary]];
[predicates addObject:[NSPredicate predicateWithFormat:@"owner <= %@", rBoundary]];
NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:predicates];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:@"entityName" inManagedObjectContext:context]];
[request setPredicate:predicate];
NSArray *results = [context executeFetchRequest:request error:nil];