Our thoughts


Industry insights and market opinion from our CEO, Simon Lee. Articles cover app development and everything Apple from our leading app developer.

Temporary Storage In Apple’s CoreData

Code

I've worked with CoreData on a number of projects covering both iPhone and iPad apps, and I must say it's pretty good. Apple's persistent storage model and their implementation of it is a good example of how an ORM (object-relational-mapping) can speed up your development process and enable developers to focus on domain level implementations without worrying too much about the underlying data structure.

Whilst CoreData is good, it can take a while for developers to get to grips with this new approach to data storage. One thing I see time and time again is the question of how to handle temporary data. One argument is that core data should only be used for the persistent data store and that any operations above this level should use other domain level objects; only mapping to a NSManagedObject model once you know you wish to persist. The second argument is that you should limit the number of duplicate domain level objects you have and utilise CoreData for the storage and handling of both temporary and persistent objects.

I am not going to discuss the merits of either approach but I am going to share an implementation of the latter; being able to work with temporary NSManagedObjects in a nice clean way.

The source code for this article can be downloaded here.

Standard CoreData Use

Assuming that we have a descendent of NSManagedObject called Person and a NSManagedObjectContext instance, the standard way to create a persisted object is by using the following code...

[objc]
Person *person = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:context];
[persistedPerson setName:@"Person 1"];
[persistedPerson setAge:[NSNumber numberWithInt:20]];

NSError *error = nil;

if(![context save:&error]) {
// Log error
}
[/objc]

As you will notice, you never actually save the object itself, instead you save the context it is associated with. This saves all changes to that have been made to it, including new objects, edited objects and deleted objects. If you had created any temporary objects on this context, they would be automatically saved too.

To Persist Or Not Persist

So how do we avoid saving an object we know is temporary, when saving the context it resides within? Well one approach is to delete a temporary object when you don't need it anymore, removing it from the context...

[objc light="true"]
[context deleteObject:person];
[/objc]

This removes the object from the context and doesn't persist it. What actually happens in this case is that CoreData realises the object was never persisted and simply removes it from memory. However, had you saved the context anywhere else between the creation and deletion calls, the object would have been persisted. Hmmmm, no good then.

A Better Approach

A much better approach is to create the entity on a nil context, that way it won't be saved because there is no context on which to save it! I found the best way to do this was to create a base domain level object which descends from NSManagedObject and contains a class method to create a disconnected instance, the method is as follows...

[objc]
+ (id)disconnectedEntity {
NSManagedObjectContext *context = [[LOStorageService instance] managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"Person" inManagedObjectContext:context];
return [[[self alloc] initWithEntity:entityDescription insertIntoManagedObjectContext:nil] autorelease];
}
[/objc]

If all of your managed objects descend from this one, then you can create temporary objects with the following code...

[objc]
Person *temporaryPerson = [Person disconnectedEntity];
[temporaryPerson setName:@"Person 2"];
[temporaryPerson setAge:[NSNumber numberWithInt:30]];
[/objc]

Perfect, now we can keep hold of this object safe in the knowledge that it won't be saved. So how do we save it? Well, we need to add it to a context, then save the context as normal. Again, I add a method in the base class to handle this...

[objc]
- (void)addToContext:(NSManagedObjectContext *)context {
[context insertObject:self];
}
[/objc]

Recursive Contexts

So why have an 'addToContext' method in a base class? It's pretty simple right, I could just do the following...

[objc]
[context insertObject:person];

NSError *error = nil;
if(![context save:&error]) {
// Log error
}
[/objc]

Well, the issue lies in relationships. When we create a temporary object, we also create any referenced objects as temporary ones too (so they also don't get persisted). The problem is we can't save our top-level object if it has related objects which are temporary; we need to add them all to the same context. That's why we have the method in the base class, assuming our person had a relationship to an object called Address (also a descendent of our base class), we would override our addToContext method in Person as follows...

[objc]
- (void)addToContext:(NSManagedObjectContext *)context {
[super addToContext:context];

for(Address *address in self.addresses) {
[address addToContext:context];
}
}
[/objc]

This ensures that our Person gets added to the context as does each of its address objects. We can repeat this process, i.e. if Address had a list of Address Line objects we would override the addToContext method in Address to add these to our context.

The Full Code

The full code for the base domain object is as follows...

[objc title="LODomainObject.h"]
#import <CoreData/CoreData.h>

@interface LODomainObject : NSManagedObject { }

+ (NSString *)entityName;
+ (id)disconnectedEntity;
- (void)addToContext:(NSManagedObjectContext *)context;

@end
[/objc]

[objc title="LODomainObject.m"]
#import "LODomainObject.h"
#import "LOStorageService.h"

@implementation LODomainObject

+ (NSString *)entityName {
[self doesNotRecognizeSelector:_cmd];
return nil;
}

+ (id)disconnectedEntity {
NSManagedObjectContext *context = [[LOStorageService instance] managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:[self entityName] inManagedObjectContext:context];
return [[[self alloc] initWithEntity:entityDescription insertIntoManagedObjectContext:nil] autorelease];
}

- (void)addToContext:(NSManagedObjectContext *)context {
[context insertObject:self];
}

@end
[/objc]

So what's this EntityName thing all about? Well it is something we use to make things easier, everywhere where you would use a string for an entity name you can call the class method on the entity itself and ask for its name, keeps things simple!

So that's about it, check out the source code which contains a simple example of this in action as well as a nice reusable storage service and base domain class.