I have been developing an application that required the SQLite database file to be replaced at runtime. This event was triggered when a new database file was downloaded from a remote server. I considered forcing the user to close the application, however this would be a poor user experience and just poor form.
One major issue was releasing the existing database correctly and then re-connecting. The last hurdle was to clear any existing references to any managed objects currently being viewed or cached.
After searching the net and some experimenting I finally have something that is working.
- (BOOL) updateDB {
if (managedObjectContext != nil) {
[managedObjectContext lock];
[managedObjectContext reset];
}
if (persistentStore != nil) {
NSError *error;
if (![persistentStoreCoordinator removePersistentStore:persistentStore error:&error]) {
NSLog(@"Unable to remove persistent store error %@, %@", error, [error userInfo]);
return FALSE;
}
}
if (managedObjectContext != nil) {
[managedObjectContext unlock];
}
if (persistentStore != nil) {
[persistentStoreCoordinator release], persistentStoreCoordinator = nil;
}
if (managedObjectContext != nil) {
[managedObjectContext release], managedObjectContext = nil;
}
if (managedObjectModel != nil) {
[managedObjectModel release], managedObjectModel = nil;
}
[self replaceDB];
[self managedObjectContext];
[self clearDBReferences];
return TRUE;
}
The managedObjectContext is initialised lazily, therefore is could be nil.
- (NSManagedObjectContext *) managedObjectContext {
if (managedObjectContext != nil) {
return managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext = [[NSManagedObjectContext alloc] init];
[managedObjectContext setPersistentStoreCoordinator: coordinator];
}
return managedObjectContext;
}
The order of the messages are important to ensure the resources are released in the reverse order they were initialised.
The [self replaceDB] simple copies the new SQLite DB into place.
The [self managedObjectContext] simple forces the Lazily initialisation.
The last message races though all controllers requesting them to clear any references to managed objects.
I simple created a ReleaseResource protocol that has a clear method. If a controller implements this protocol then it can simple release any managed objects.
For example, I have a controller that subclasses UITableViewController.
-(void)clear {
self.fetchedResultsController = nil;
[self.tableView reloadData];
}
This code will obviously change depending on your application layout. This method races through the controllers looking for classes that implement the ReleaseResource protocol.
- (void) clearDBReferences {
for (UIViewController *controller in tabBarController.viewControllers) {
// for iPad
if ([controller isKindOfClass:[UISplitViewController class]]) {
NSLog(@"Clearing iPAD DB References");
for (UINavigationController *navController in ((UISplitViewController *) controller).viewControllers) {
[navController popToRootViewControllerAnimated:FALSE];
for (UINavigationController *rootController in ((UINavigationController *) navController).viewControllers) {
if ([rootController conformsToProtocol:@protocol(ReleaseResources)]) {
[rootController performSelector:@selector(clear)];
}
}
}
}
// for iPhone
if ([controller isKindOfClass:[UINavigationController class]]) {
UINavigationController *navController = ((UINavigationController *) controller);
NSLog(@"Clearing iPhone DB References");
[navController popToRootViewControllerAnimated:FALSE];
for (UINavigationController *rootController in ((UINavigationController *) navController).viewControllers) {
if ([rootController conformsToProtocol:@protocol(ReleaseResources)]) {
[rootController performSelector:@selector(clear)];
}
}
}
}
}