Sqlite and CoreData in iOS, repository pattern

I haven’t see too many articles talking about iOS Core-Data development yet, so I documented something important here for future reference.

  1.  No need to create ID property as identity column for your model, in fact, id is a reserved keyword for cocoa, IDE will looks weird for id property in model. core-data will generate a few extra columns in your model table, including ZID, Z…, all  have a Z prefix.
  2. All number like fields will be mapped to NSNumber.
  3. Create app with core-data support, in XCode4, single view app template doesn’t have this core-data support option, try create another project in different project template, then copy the generated core-data code from appDelegate into your project.
  4. If you have your own existing database which will be installed with app bundle, you need to manually copy it from bundle to document folder to make it writable.
  5. New object needs to save into database later should created by insertNewObjectForEntityForName.
  6. Hold the managedObjectContext till the app terminate, that’s why core-data code were in appDeledate, if move into you own class, create retain property.

Personnly, I like to implement repository pattern for all coredata operations, including initialize. My sample repository looks like this:

@interface iCWOccurrenceRepository()
- (NSMutableArray *)retrieveAllOccurrencesForCity:(NSString*)city sinceDate:(NSDate*)startDate;
@end

@implementation iCWOccurrenceRepository

@synthesize managedObjectContext = __managedObjectContext;
@synthesize managedObjectModel = __managedObjectModel;
@synthesize persistentStoreCoordinator = __persistentStoreCoordinator;

NSString * DBNAME = @"mydb.sqlite";

- (void)saveANew:(NSArray*) line{
    NSManagedObjectContext * context = self.managedObjectContext;
    NSManagedObject *newOccurrence = [NSEntityDescription
                                      insertNewObjectForEntityForName:@"Occurrence"
                                      inManagedObjectContext:context];

    Occurrence* oToSave = ((Occurrence*)newOccurrence);
    oToSave.id = [NSNumber numberWithInt:[[line objectAtIndex:0] intValue]];
    oToSave.city = [line objectAtIndex:1];
//...
    [self saveContext];

}
- (void)saveContext
{
    NSError *error = nil;
    NSManagedObjectContext *managedObjectContext = __managedObjectContext;
    if (managedObjectContext != nil)
    {
        if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error])
        {
            /*
             Replace this implementation with code to handle the error appropriately.

             abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
             */
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }
    }
}

#pragma mark - Core Data stack

/**
 Returns the managed object context for the application.
 If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
 */
- (NSManagedObjectContext *)managedObjectContext
{
    if (__managedObjectContext != nil)
    {
        return __managedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil)
    {
        __managedObjectContext = [[NSManagedObjectContext alloc] init];
        [__managedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return __managedObjectContext;
}

/**
 Returns the managed object model for the application.
 If the model doesn't already exist, it is created from the application's model.
 */
- (NSManagedObjectModel *)managedObjectModel
{
    if (__managedObjectModel != nil)
    {
        return __managedObjectModel;
    }
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"iCW" withExtension:@"momd"];
    __managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    return __managedObjectModel;
}

/**
 Returns the persistent store coordinator for the application.
 If the coordinator doesn't already exist, it is created and the application's store added to it.
 */
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (__persistentStoreCoordinator != nil)
    {
        return __persistentStoreCoordinator;
    }

    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:DBNAME];

    NSError *error = nil;
    __persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
    {
         [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];

        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    return __persistentStoreCoordinator;
}

#pragma mark - Application's Documents directory

/**
 Returns the URL to the application's Documents directory.
 */
- (NSURL *)applicationDocumentsDirectory
{
    return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}

-(void)createEditableCopyOfDatabaseIfNeeded {
	//
	NSFileManager *fileManager = [NSFileManager defaultManager];
	NSURL *documentDirectory = [self applicationDocumentsDirectory];
	NSString *writeableDBPath = [[documentDirectory
								  URLByAppendingPathComponent:DBNAME]
								 path];
	NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath]
							   stringByAppendingPathComponent:DBNAME];

	BOOL defaultDbExists = [fileManager fileExistsAtPath:defaultDBPath];
	BOOL dbExistsInDocFolder = [fileManager fileExistsAtPath:writeableDBPath];

	if (!dbExistsInDocFolder && defaultDbExists) {

		NSError *error;
		BOOL success = [fileManager copyItemAtPath:defaultDBPath toPath:writeableDBPath error:&error];

		if (!success) {
            NSAssert1(0, @"Failed to create writable database file with message '%@'.",
                      [error localizedDescription]);
		}
	}

}

- (NSMutableArray *)retrieveAllOccurrencesForCity:(NSString*)city sinceDate:(NSDate*)startDate{

	NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
	NSFetchRequest *request = [[NSFetchRequest alloc] init];
	NSEntityDescription *entity = [NSEntityDescription entityForName:@"Occurrence"
											  inManagedObjectContext:managedObjectContext];
	[request setEntity:entity];

	NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]
										initWithKey:@"id" ascending:NO];

	NSArray *sortDescriptors = [[NSArray alloc]
								initWithObjects:sortDescriptor, nil];

	[request setSortDescriptors:sortDescriptors];

	NSPredicate *bPredicate =
    [NSPredicate predicateWithFormat:@"city == %@ and date > %@", city, startDate];

    [request setPredicate:bPredicate];

	NSError *error;
	NSMutableArray *fetchResults = [[managedObjectContext executeFetchRequest:request
                                                                        error:&error] mutableCopy] ;

	if (fetchResults == nil) {
		// handle the error
	}
	return fetchResults;

}
@end

Again, this repository class needs to be a strong retain property in caller.

Advertisements

One thought on “Sqlite and CoreData in iOS, repository pattern

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