Simplifying Core Data setup

Most of developers use Core Data for persistence in iOS apps. I’d like to share my view on how to simplify Core Data stack setup and usage. Some of these could be used alone, or combined with other approaches.

Let’s start with initial stack setup. Here I focus on simple setup – one store which should automatically migrate between versions. So, we’ll have one NSPersistentStoreCoordinator and could have several NSManagedObjectContext‘s created as needed.

I’m adding several new methods to NSManagedObjectContext class using Objective-C category. Singleton objects are defined using dispatch_once. Let’s start with NSPersistentStoreCoordinator.

+(NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    static dispatch_once_t onceToken;
    static NSPersistentStoreCoordinator * _persistentStoreCoordinator;
    dispatch_once(&onceToken, ^{
        NSURL *storeURL = [[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
                                                                   inDomains:NSUserDomainMask] lastObject]
                           URLByAppendingPathComponent:@"data.sqlite"];
        
        NSError * __autoreleasing error;
        NSManagedObjectModel * managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:@[ [NSBundle mainBundle] ]];

        _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:managedObjectModel];

        if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
                                                       configuration:nil
                                                                 URL:storeURL
                                                             options:@{ NSMigratePersistentStoresAutomaticallyOption: @(YES),
                                                                        NSInferMappingModelAutomaticallyOption: @(YES) }
                                                               error:&error])
        {
            NSLog(@"Persistent store not opened: %@", error);
            abort();
        }
    });
    
    return _persistentStoreCoordinator;
}

Code creates store at Documents directory with data.sqlite name. Each NSPersistentStoreCoordinator needs NSManagedObjectModel. There is a way of getting all the models from application bundle, so we don’t need to know model name. In most cases this code would be enough.

Next. Let’s add some factory methods for creating NSManagedObjectContext‘s.

+(instancetype)contextWithConcurrencyType:(NSManagedObjectContextConcurrencyType)concurrencyType
{
    NSManagedObjectContext *context;
    if ([self persistentStoreCoordinator])
    {
        context = [[self alloc] initWithConcurrencyType:concurrencyType];
        context.persistentStoreCoordinator = [self persistentStoreCoordinator];
        context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
    }
    
    return context;    
}

+(instancetype)context
{
    return [self contextWithConcurrencyType:NSConfinementConcurrencyType];
}

These methods are used to create NSManagedObjectContext objects with proper concurrency type.

I also prefer to use single shared context for main queue (typically, it is read-only).

+(instancetype)sharedContext
{
    static dispatch_once_t onceToken;
    static NSManagedObjectContext *_sharedContext;
    dispatch_once(&onceToken, ^{
        _sharedContext = [self contextWithConcurrencyType:NSMainQueueConcurrencyType];
        [[NSNotificationCenter defaultCenter] addObserver:_sharedContext
                                                 selector:@selector(mergeChangesToSharedContext:)
                                                     name:NSManagedObjectContextDidSaveNotification
                                                   object:nil];
    });
    return _sharedContext;
}

-(void)mergeChangesToSharedContext:(NSNotification*)notification
{
    NSManagedObjectContext *savingContext = notification.object;
    if ((savingContext.persistentStoreCoordinator == self.persistentStoreCoordinator) && 
        (self != savingContext) && 
        (notification.userInfo))
    {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self mergeChangesFromContextDidSaveNotification:notification];
            [self processPendingChanges];
        });
    }
}

As you can see, this context is an observer for NSManagedObjectContextDidSaveNotification – so, it will be automatically updated on every save of all other contexts.

This covers most of typical Core Data stack setup.

I’d also like to add some ideas to make life easier when dealing with NSManagedObject subclasses. It is generally a good idea to name entities in a model the same as class names (do not forget to add class prefixes). If you do so, then you’ll avoid one magic string in your code – you could use NSStringFromClass to get entity name for methods requiring it.

We can add some methods to NSManagedObject using Objective-C category. These methods will be available to all subclasses as well.

+(NSString*)entityName
{
    return NSStringFromClass(self);
}

+(NSFetchRequest*)fetchRequest
{
    return [NSFetchRequest fetchRequestWithEntityName:[self entityName]];
}

+(instancetype)insertNewObjectIntoManagedObjectContext:(NSManagedObjectContext*)context
{
    return [NSEntityDescription insertNewObjectForEntityForName:[self entityName]
                                         inManagedObjectContext:context];
}

+(NSArray*)allObjectsInManagedObjectContext:(NSManagedObjectContext*)context
{
    NSError * __autoreleasing error;
    NSArray *objects = [context executeFetchRequest:[self fetchRequest] error:&error];
    if ((!objects) || (error))
    {
        NSLog(@"Problems fetching objects: %@", error);
    }
    return objects;
}

These methods cover some of most typical uses for NSManagedObject‘s. These do not solve problem with property names, which still will need either constants defined in code, or again – simple magic strings.
To minimize such problems I prefer to add all methods of querying and inserting objects into concrete NSManagedObject subclass category. Therefore, there will be only one place where I need to deal with key names and it will be easier to find errors and fix them.

That’s it for today. I’ll be covering some other Core Data topics later on.

As always, read Apple’s documentation – Core Data Programming Guide.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s