“Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live” ― John Woods
If you don’t know what’s CoreData you are in the wrong place. I suggest you go through WWDC 2010 video
Mastering CoreData, before you proceed with the blog.
CoreData is easy to start off with but difficult to master, and the generated code that Apple provides when you create a new project with
CoreData doesn’t help either. In the generated code project you will find methods like persistentStoreCoordinator, managedObjectContext, saveContext related to CoreData are being
implemented by the AppDelegate, which completely violates the Single Responsibility Principle.
Methods related to setting up CoreData, upgrading CoreData , creating ManagedObjectContext etc should be present in a different class. This kind of
project is great to spike out things, but you never write this in production code.
If you are not convinced with my argument, try to answer the following questions -
What would you do when you want to manage 2 different CoreData databases for 2 separate purposes ?
How could you TDD this kind of code when everything you need is inside AppDelegate ?
Why is CoreData methods tied so tightly with something like AppDelegate ?
Why the hell do you have to type cast like the below every time you want to access the NSManagedObjectContext.
According to me, NSManagedObjectContext should be created and passed along to different functions when needed. Using NSManagedObjectContext with singleton pattern (As suggested by
code generated by Apple) reduces the power that CoreData provides. Context is there so that you can create several at a time, use separate contexts in separate threads, discard a
context without saving it not needed, or merge a 2 or 3 contexts.
So, extracting methods out into a NSManagedObjectContext subclass will make things look something like this:
@implementationTTManagedObjectContext+(TTManagedObjectContext*)managedObjectContextForManagedObjectModel:(NSString*)momNameandSqliteFileName:(NSString*)filename{NSPersistentStoreCoordinator*coordinator=[selfpersistentStoreCoordinatorForManagedObjectModel:momNameandFilename:filename];TTManagedObjectContext*context=[[TTManagedObjectContextalloc]init];[contextsetPersistentStoreCoordinator:coordinator];returncontext;}+(NSManagedObjectModel*)managedObjectModelForName:(NSString*)momName{NSURL*modelURL=[[NSBundlebundleForClass:[selfclass]]URLForResource:momNamewithExtension:@"momd"];if(modelURL==nil){NSLog(@"Could not find file:%@.momd",momName);abort();}NSManagedObjectModel*managedObjectModel=[[NSManagedObjectModelalloc]initWithContentsOfURL:modelURL];returnmanagedObjectModel;}+(NSPersistentStoreCoordinator*)persistentStoreCoordinatorForManagedObjectModel:(NSString*)momandFilename:(NSString*)filename{NSPersistentStoreCoordinator*persistentStoreCoordinator=[[NSPersistentStoreCoordinatoralloc]initWithManagedObjectModel:[selfmanagedObjectModelForName:mom]];NSError*error=nil;if(filename!=nil){NSURL*storeURL=[[selfapplicationDocumentsDirectory]URLByAppendingPathComponent:filename];[persistentStoreCoordinatoraddPersistentStoreWithType:NSSQLiteStoreTypeconfiguration:nilURL:storeURLoptions:nilerror:&error];}else{[persistentStoreCoordinatoraddPersistentStoreWithType:NSInMemoryStoreTypeconfiguration:nilURL:niloptions:nilerror:&error];}if(error){NSLog(@"ERROR OCCURED:%@",error);abort();}returnpersistentStoreCoordinator;}+(NSURL*)applicationDocumentsDirectory{return[[[NSFileManagerdefaultManager]URLsForDirectory:NSDocumentDirectoryinDomains:NSUserDomainMask]lastObject];}@end
Now you can write unit tests such as the following, plus your code is now modular and ready for re-use:
-(void)setUp{[supersetUp];logContext=[TTManagedObjectContextmanagedObjectContextForManagedObjectModel:@"Log"andSqliteFileName:nil];charactersContext=[TTManagedObjectContextmanagedObjectContextForManagedObjectModel:@"Characters"andSqliteFileName:nil];// Note: SqliteFilename is nil which according to our new implementation makes in-memory db, so the unit tests now run faster// and you do not need to worry about clearing the db after running the tests.}-(void)testSaveInCharacters{NSManagedObject*batman=[NSEntityDescriptioninsertNewObjectForEntityForName:@"SuperHero"inManagedObjectContext:charactersContext];[batmansetValue:@"Batman"forKey:@"name"];[batmansetValue:@3forKey:@"power"];[batmansetValue:@5forKey:@"brains"];NSManagedObject*superman=[NSEntityDescriptioninsertNewObjectForEntityForName:@"SuperHero"inManagedObjectContext:charactersContext];[supermansetValue:@"Superman"forKey:@"name"];[supermansetValue:@5forKey:@"power"];[supermansetValue:@1forKey:@"brains"];NSManagedObject*log=[NSEntityDescriptioninsertNewObjectForEntityForName:@"Log"inManagedObjectContext:logContext];[logsetValue:@"Batman wins !!!"forKey:@"message"];[logsetValue:@1forKey:@"priority"];[charactersContextsave:nil];[logContextsave:nil];NSFetchRequest*fetchRequestCharacter=[[NSFetchRequestalloc]initWithEntityName:@"SuperHero"];NSFetchRequest*fetchRequestLog=[[NSFetchRequestalloc]initWithEntityName:@"Log"];NSArray*superHeros=[charactersContextexecuteFetchRequest:fetchRequestCharactererror:nil];NSArray*logs=[logContextexecuteFetchRequest:fetchRequestLogerror:nil];XCTAssertEqual(superHeros.count,2);XCTAssertEqual(logs.count,1);XCTAssertEqual([logs[0]valueForKey:@"message"],@"Batman wins !!!");}
This has really modularised things for us, in the next part we will look how can we take this even further.
You can find the full code on my github repo. CoreDataExample