A few months ago when I was developing a traveling planning iOS app for iPhone app, I was presented with a problem. Which is to synchronize data between the iOS app and the Web app, as of current, I know no such framework exists that can do this out-of-the-box. So, instead this problem grew quickly to become several bigger problem. Because there and then, I need to learn how to post and get information to the backend that functions as Restful services. And that brings me to RestKit, one heck of a framework for Restful services for iOS.
It wasn’t long before I was beginning to understand how it works, but then I have another problem. The Core Data, is pretty high level, and it abstracts a lot of stuff which renders editing SQLLite impossible. So, how do I go about solving this problem? Even if I can get the data to and from the Restful services. How do I make changes to them, to update, delete or retrieve the data? It took me a while to come up with a class that can solve just this problem.
The header file for it
#import <Foundation/Foundation.h> #import "Asset.h" //Asset class being the Model object that you want to do CRUD with. @interface SynchroAsset : NSObject <NSFetchedResultsControllerDelegate> { @private NSFetchedResultsController *fetchedResultsController; NSManagedObjectContext *managedObjectContext; } @property (nonatomic, retain) NSString *compareUUIDString; @property (nonatomic, retain) NSString *queryUUIDString; @property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController; @property (nonatomic, retain) NSManagedObjectContext *managedObjectContext; -(void)initWithSearchString:(NSString *)searchString; -(Asset *)countReturnSearchString:(NSString *)searchString withQuery:(NSString *)queryString; -(NSArray *)returnAllTripWithSearchString:(NSString *)searchString withQuery:(NSString *)queryString; @end
And the implementation file
#import "SynchroAsset.h" @implementation SynchroAsset @synthesize managedObjectContext, fetchedResultsController; @synthesize compareUUIDString; @synthesize queryUUIDString; - (id)init { return [super init]; } -(void)initWithSearchString:(NSString *)searchString { compareUUIDString = [NSString stringWithString:searchString]; queryUUIDString = [NSString stringWithFormat:@"nid"]; [NSFetchedResultsController deleteCacheWithName:nil]; NSError *error = nil; if (![[self fetchedResultsController] performFetch:&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. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button. */ NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } // NSLog(@"Syncrho: %d", [[fetchedResultsController fetchedObjects] count]); } -(Asset *)countReturnSearchString:(NSString *)searchString withQuery:(NSString *)queryString { Asset *retrievedGroup = nil; compareUUIDString = [NSString stringWithString:searchString]; queryUUIDString = [NSString stringWithString:queryString]; [NSFetchedResultsController deleteCacheWithName:nil]; NSError *error = nil; if (![[self fetchedResultsController] performFetch:&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. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button. */ NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } if ([[fetchedResultsController fetchedObjects] count] > 0) { retrievedGroup = (Asset *)[[fetchedResultsController fetchedObjects] objectAtIndex:0]; } //NSLog(@"Asset Count: %d", [[fetchedResultsController fetchedObjects] count]); return retrievedGroup; } -(NSArray *)returnAllTripWithSearchString:(NSString *)searchString withQuery:(NSString *)queryString { compareUUIDString = [NSString stringWithString:searchString]; queryUUIDString = [NSString stringWithString:queryString]; [NSFetchedResultsController deleteCacheWithName:nil]; NSError *error = nil; if (![[self fetchedResultsController] performFetch:&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. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button. */ NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } return [fetchedResultsController fetchedObjects]; } - (NSFetchedResultsController *)fetchedResultsController { // Set up the fetched results controller if needed. if (fetchedResultsController == nil) { // Create the fetch request with init, so no entity is being specified here. NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; // Edit the entity name as appropriate. NSEntityDescription *entity = [NSEntityDescription entityForName:@"Asset" inManagedObjectContext:managedObjectContext]; [fetchRequest setEntity:entity]; if (![queryUUIDString isEqualToString:@"*"]) { NSPredicate *requestPredicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:@"(%@ like '%@')", queryUUIDString, compareUUIDString]]; [fetchRequest setPredicate:requestPredicate]; } // Edit the sort key as appropriate. NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"nid" ascending:YES]; NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil]; [fetchRequest setSortDescriptors:sortDescriptors]; // Edit the section name key path and cache name if appropriate. // nil for section name key path means "no sections". NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:@"RootMainAssetIDSearch"]; aFetchedResultsController.delegate = self; self.fetchedResultsController = aFetchedResultsController; [aFetchedResultsController release]; [fetchRequest release]; [sortDescriptor release]; [sortDescriptors release]; }else { fetchedResultsController = nil; NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; // Edit the entity name as appropriate. NSEntityDescription *entity = [NSEntityDescription entityForName:@"Asset" inManagedObjectContext:managedObjectContext]; [fetchRequest setEntity:entity]; if (![queryUUIDString isEqualToString:@"*"]) { NSPredicate *requestPredicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:@"(%@ like '%@')", queryUUIDString, compareUUIDString]]; [fetchRequest setPredicate:requestPredicate]; } // Edit the sort key as appropriate. NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"nid" ascending:YES]; NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil]; [fetchRequest setSortDescriptors:sortDescriptors]; // Edit the section name key path and cache name if appropriate. // nil for section name key path means "no sections". NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:@"RootMainAssetIDSearch"]; aFetchedResultsController.delegate = self; self.fetchedResultsController = aFetchedResultsController; [aFetchedResultsController release]; [fetchRequest release]; [sortDescriptor release]; [sortDescriptors release]; } return fetchedResultsController; } #pragma mark Delegate Method of NSFetchedResultsController /** Delegate methods of NSFetchedResultsController to respond to additions, removals and so on. */ - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { // The fetch controller is about to start sending change notifications, so prepare the table view for updates. } - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { } - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type { } - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { // The fetch controller has sent all current change notifications, so tell the table view to process all updates. } -(void)dealloc { if (managedObjectContext) { self.fetchedResultsController.delegate = nil; self.fetchedResultsController = nil; [managedObjectContext release]; } [fetchedResultsController release]; [super dealloc]; } @end
This class basically retrieve the desired data with some ID that I have assigned beforehand, and this ID is unique in everyday and it’s communicated across the iOS app and also the Web app. So, to make changes to any particular data. All I need to know is this ID and the rest falls into its place.
But this method is not without its pitfall, if you use this class. You will soon realize error like “Core Data Critical Error” will come up somewhere in your app’s logic and you will be scratching your head wondering what the hell is going on. The main reason here is because I am using NSFetchedResultsController, which is tricky to play with. Others suggests that I should do this to the NSManagedContext before updating/deleting/moving
[nsManagedObjectContext lock]; //Do the update logic here, and when it finishes, do "unlock" [nsManagedObjectContext unlock]; //Never do [nsManagedObjectContext reset]; //Because resetting your MOC will means that any data/model that is not yet saved to NSPersistentStore, will be deleted there and then. And you will be wondering why the data stays outdated. //You will also realize that I set the delegate of NSFetchedResultsController to nil before trying to release its memory if (managedObjectContext) { self.fetchedResultsController.delegate = nil; self.fetchedResultsController = nil; [managedObjectContext release]; } [fetchedResultsController release]; //That's because setting the delegate to nil allows us to safely release the fetchedResultsController without having the error of releasing memory of it when the app is still utilizing it. And that's proven quite useful to avoid such crashes.
And there you go, the results of my 1 week research and brain bashing for simple data retrieve and edit for Core Data using simple ID string.