I have a very strange problem where Core Data works fine in development (debug) builds, but in distribution (release) builds distributed over TestFlight, my NSManagedObjectContext objects are not saving correctly.
I'm observing two issues:
What could be causing this ONLY in distribution builds? As far as I know, provisioning profiles don't affect Core Data.
Here's two relevant functions of my Core Data stack:
- (void)initializeCoreData
{
NSLog(@"%s", __FUNCTION__);
if ([self managedObjectContext]) return;
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"XXX" withExtension:@"momd"];
NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
NSAssert(mom, @"%@:%@ No model to generate a store from", [self class], NSStringFromSelector(_cmd));
NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
NSAssert(coordinator, @"Failed to initialize coordinator");
self.privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
self.privateContext.persistentStoreCoordinator = coordinator;
self.managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
self.managedObjectContext.parentContext = self.privateContext;
// Persistence store
NSMutableDictionary *options = [NSMutableDictionary dictionary];
options[NSMigratePersistentStoresAutomaticallyOption] = @YES;
options[NSInferMappingModelAutomaticallyOption] = @YES;
// options[NSSQLitePragmasOption] = @{ @"journal_mode":@"DELETE" };
NSURL *documentsURL = [[NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
// TODO: use real store url
NSURL *storeURL = [documentsURL URLByAppendingPathComponent:@"Data2.sqlite"];
NSError *error = nil;
NSAssert([coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error], @"Error initializing PSC: %@\n%@", [error localizedDescription], [error userInfo]);
}
- (void)save;
{
if (![[self privateContext] hasChanges] && ![[self managedObjectContext] hasChanges]) return;
[[self managedObjectContext] performBlockAndWait:^{
NSError *error = nil;
NSAssert([[self managedObjectContext] save:&error], @"Failed to save main context: %@\n%@", [error localizedDescription], [error userInfo]);
[[self privateContext] performBlock:^{
NSError *privateError = nil;
NSAssert([[self privateContext] save:&privateError], @"Error saving private context: %@\n%@", [privateError localizedDescription], [privateError userInfo]);
}];
}];
}
Any thoughts?
Update: I tried launching a debug build and storing something in the Core Data database and it persists fine between launches, then deployed a release build without deleting the app to keep the data and the database seems empty, then deployed a debug build again and the existing database loads fine. So it seems that the release build has an issue with accessing the database file for some reason. Any thoughts why that would happen?
NSAssert
does not normally run in a Release Build as NS_BLOCK_ASSERTIONS
is defined as part of the standard Xcode template. Or from the docs
IMPORTANT Do not call functions with side effects in the condition parameter of this macro. The condition parameter is not evaluated when assertions are disabled, so if you call functions with side effects, those functions may never get called when you build the project in a non-debug configuration.
Basically don't do stuff you care about in an Assert. Its a debugging test. You really want be a bit more gentle here and soft fail.
if([[self managedObjectContext] save:&error] == NO){
NSLog(@"Failed to save main context: %@\n%@", [error localizedDescription], [error userInfo]);
}
else {
[[self privateContext] performBlock:^{
NSError *privateError = nil;
if([[self privateContext] save:&privateError] == NO){
NSLog(@"Error saving private context: %@\n%@", [privateError localizedDescription], [privateError userInfo]);
}
}];
}