Getting to the Core


I’ve been using Core Data in the app I’m working on. I’m no expert, not by a long way, I’m a beginner with a bit of experience from my first few encounters, so bear in mind that there’s probably a much better way of doing a lot of this. We’re all learning something though!

I’m using Core Data with the Document model, using a UIManagedDocument for persistence. This is supposed to make iCloud sync fairly straightforward (if you believe that… you’re a lot less jaded than I am with Apple’s web services!) but I’m just using it locally at present.

Things I like - the ease of use with a collection view and table view. I’m making heavy use of both of these, and being able to use a fetched results controller helps simplify my code no end. For example, the DataSource for my base TableView class is as simple as this:

#pragma mark - UITableViewDataSource

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    NSInteger sections = [[self.fetchedResultsController sections] count];
    return sections;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    NSInteger rows = 0;
    if ([[self.fetchedResultsController sections] count] > 0) {
        id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
        rows = [sectionInfo numberOfObjects];
    }
    return rows;
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
    return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
}

- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
    return [self.fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
}

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
    return [self.fetchedResultsController sectionIndexTitles];
}

And with that, I barely need to worry about the datasource again. Great separation, with no need to worry about implementing methods as I go.

I have had a few problems with it though, one of which was subtle and a big headache to get fixed - actual persistance! Adding records to the core data context and relying on autosave to persist them for you will not cut it, you need to use the updateChangeCount method on the UIManagedDocument instance. This is a bit of a pain, as you can’t just rely on passing the context around or grabbing the context from one of your UIManagedObjects, so you really end up using a singleton to get hold of the document. It’s pretty straightforward to do, just use a class like this:

@implementation SharedDatabaseDocument

@synthesize document = _document;

static SharedDatabaseDocument *_sharedInstance;

+ (SharedDatabaseDocument *)sharedDocumentHandler
{
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        _sharedInstance = [[self alloc] init];
    });
    
    return _sharedInstance;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask] lastObject];
        url = [url URLByAppendingPathComponent:@"Your Directory Name Here"];
        
        self.document = [[UIManagedDocument alloc] initWithFileURL:url];
        
        // Set our document up for automatic migrations
        NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                                 [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                                 [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
        self.document.persistentStoreOptions = options;
        
    return self;
}

- (void)performWithDocument:(OnDocumentReady)onDocumentReady
{
    void (^OnDocumentDidLoad)(BOOL) = ^(BOOL success) {
        onDocumentReady(self.document);
    };
    
    if (![[NSFileManager defaultManager] fileExistsAtPath:[self.document.fileURL path]]) {
        [self.document saveToURL:self.document.fileURL
                forSaveOperation:UIDocumentSaveForCreating
               completionHandler:OnDocumentDidLoad];
    } else if (self.document.documentState == UIDocumentStateClosed) {
        [self.document openWithCompletionHandler:OnDocumentDidLoad];
    } else if (self.document.documentState == UIDocumentStateNormal) {
        OnDocumentDidLoad(YES);
    }
}

I should say - the above is not all my code, I’ve had help from various places in StackOverflow, but it’s been a while so attribution isn’t really doable :-(.

Once you’ve got that singleton, you can call updateChangeCount on it to your hearts content, and it will save appropriately.

I’m using Core Data despite its poor reputation, since I’m a big believer in using what Apple provides unless there’s an extremely good reason to use a third party framework. Third party work is fantastic, especially when used to do something Apple don’t provide an API for, however in an area like this, I feel that the long term benefits of staying with Apple’s API, in terms of upgrade path, general use of the same code by other developers are well worth the occasional extra legwork over a third party framework. And now, given that Apple are talking so much about dogfooding these APIs, they should get better even quicker!.