I'm using Core Data in an existing application. Now I want to integrate iCloud so that the user can synchronize their contents between their iOS-devices. To do that I've written the following code for my NSPersistentStoreCoordinator (of course the placeholders are filled out in my code):
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator_ != nil) {
return persistentStoreCoordinator_;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"<DB_Name>.sqlite"];
persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSPersistentStoreCoordinator* psc = persistentStoreCoordinator_;
if (IOS_VERSION_GREATER_THAN_OR_EQUAL_TO(#"5.0")) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSFileManager *fileManager = [NSFileManager defaultManager];
// Migrate datamodel
NSDictionary *options = nil;
// this needs to match the entitlements and provisioning profile
NSURL *cloudURL = [fileManager URLForUbiquityContainerIdentifier:#"<App_Identifier>"];
NSString* coreDataCloudContent = [[cloudURL path] stringByAppendingPathComponent:#"data"];
if ([coreDataCloudContent length] != 0) {
// iCloud is available
cloudURL = [NSURL fileURLWithPath:coreDataCloudContent];
options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
#"<App_Name>.store", NSPersistentStoreUbiquitousContentNameKey,
cloudURL, NSPersistentStoreUbiquitousContentURLKey,
nil];
} else {
// iCloud is not available
options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
}
NSError *error = nil;
[psc lock];
if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
[psc unlock];
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"asynchronously added persistent store!");
[[NSNotificationCenter defaultCenter] postNotificationName:#"RefetchAllDatabaseData" object:self userInfo:nil];
});
});
} else {
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
NSError *error = nil;
if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
}
return persistentStoreCoordinator_;
}
With that code all new added data records are synchronized automatically between all iOS-devices, so that works exactly the way it should!
But what I want is that also all the existing data records are synced between all devices; that doesn't work yet. The existing records are still available within the app and can be used, but they are not synchronized.
What do I need to do to get all the existing data records synced with iCloud too? I've experimented a bit with the method
migratePersistentStore:toURL:options:withType:error:
but without any success.
I'm very grateful for any help!
Look up the WWDC 2012 session Using CoreData with iCloud. They discuss this during the final section, under the topic of seeding. They also have some sample code that you can get from the site that has an example. In short, you will have to open 2 persistent stores, set a fetch size, then grab batches from the source, loop through them and add them to the new store, and at batch size intervals, save and reset. Pretty simple.
Related
I've got some weird problems with Core Data in my iOS which I cannot seem to reproduce, it just happens from time to time with some users that report it. The error I get from my iOS crash reports:
CoreData: -[NSPersistentStoreCoordinator _coordinator_you_never_successfully_opened_the_database_so_saving_back_to_it_is_kinda_hard:] + 56
Here is a screenshot (left out the product name):
The hard thing is that I don't get any search results on that error. Here is my (relevant) code:
saving:
-(void)save
{
if(!self.horecaMOC.hasChanges)return;
NSError *error;
[self.horecaMOC save:&error];
if(error)
{
NSLog(#"save error %#",error.localizedDescription);
}
}
MOC:
-(NSManagedObjectContext*)horecaMOC
{
if(!_horecaMOC)
{
NSPersistentStoreCoordinator *coordinator = self.horecaPSC;
if (coordinator != nil) {
_horecaMOC = [[NSManagedObjectContext alloc] init];
[_horecaMOC setPersistentStoreCoordinator:coordinator];
}
}
return _horecaMOC;
}
PSC:
-(NSPersistentStoreCoordinator*)horecaPSC
{
if(!_horecaPSC)
{
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"horeca.sqlite"];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
NSError *error = nil;
_horecaPSC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.horecaMOM];
if (![_horecaPSC addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
return _horecaPSC;
}
MOM:
-(NSManagedObjectModel*)horecaMOM
{
if(!_horecaMOM)
{
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"poi" withExtension:#"momd"];
_horecaMOM = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
}
return _horecaMOM;
}
It seems like the setup is OK here, because 99% of the time it works, but sometimes I get that error that I did not open the database. Since I can't debug it's hard to figure out what's the cause. Might the PSC nil? And why would that then be? Also, I know that a MOC should be bound to 1 thread only, and since I can't get it to crash I don't think there could be an issue regarding this?
Thanks for any advice!
in my app i have to store core data database. And i have some groups and folders with default data (audiofiles, maptiles, etc) in the xcode project navigator.
I found a lot about preventing files from being backed up like:
What i have done:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"db.sqlite"];
NSLog(#"%#", storeURL.path);
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
//NSDictionary *options = #{NSMigratePersistentStoresAutomaticallyOption:#YES, NSInferMappingModelAutomaticallyOption:#YES};
/*NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];*/
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
[self addSkipBackupAttributeToItemAtURL:storeURL];
return _persistentStoreCoordinator;
}
- (NSURL *)applicationDocumentsDirectory
{
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
preventing method:
- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);
NSError *error = nil;
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
forKey: NSURLIsExcludedFromBackupKey error: &error];
if(!success){
NSLog(#"Error excluding %# from backup %#", [URL lastPathComponent], error);
}
return success;
}
(The minimum target iOS version is 7.0)
Is this enough? How can i check if the app now prevent backing up the core data database?
Before i added the addSkipBackupAttributeToItemAtURL method i checked the apps storage and found nothing under documents and data. I only found 3.1 MB under backups -> my iphone
- Install and launch your app
- Go to Settings > iCloud > Storage & Backup > Manage Storage
- If necessary, tap "Show all apps"
- Check your app's storage
You are doing it right.
Following the Apple Technical Q&A 1719: How do I prevent files from being backed up to iCloud and iTunes? you should mark with the "do not back up" attribute.
For NSURL objects, add the NSURLIsExcludedFromBackupKey attribute to prevent the corresponding file from being backed up. For CFURLRef objects, use the corresponding kCFURLIsExcludedFromBackupKey attribute.
To know which folder is more appropriated to store you Core Data database you can check the iOS Data Storage Guidelines.
Core Data fetch operation is slow with a Read-only sqlite database. Can I improve its performance?
Background: I have a core-data model with 2 configurations. One for the default application store, and another for seed-data (SeedData.sqlite). The SeedData.sqlite is placed inside the application bundle, and is setup as a Read-only store. The SeedData.sqlite contains approximately 6500 records.
This is how I setup the persistent store:
// 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 {
NSLog(#"%s", __FUNCTION__);
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
// Attempt to load the persistent store
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
// Create the default/ user model persistent store
{
NSString *storeFileName = [JTCoreDataManager defaultStoreName];
NSString *configuration = #"AppName";
NSURL *storeURL = [[self applicationLocalDatabaseDirectory] URLByAppendingPathComponent:storeFileName];
// Define the Core Data version migration options
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:configuration
URL:storeURL
options:options
error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
// Create the seed data persistent store
{
NSURL *seedDataURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"SeedData" ofType:#"sqlite"]];
NSString *configuration = #"SeedData";
// Define the Core Data version migration options
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
[NSNumber numberWithBool:YES], NSReadOnlyPersistentStoreOption,
nil];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:configuration
URL:seedDataURL
options:options
error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]); abort();
abort();
}
}
return _persistentStoreCoordinator;
}
The fetched results controller is setup like this:
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Seed"];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"title" ascending:YES];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"magicOn == %#", [NSNumber numberWithBool:YES]];
[fetchRequest setPredicate:predicate];
[fetchRequest setSortDescriptors:#[sortDescriptor]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
NSError *error;
BOOL success = [fetchedResultsController performFetch:&error];
if (!success) {
NSLog(#"%#", [error localizedDescription]);
}
I have an existing app (available in appStore) that I want to reboot from scratch, and make it as an update. So I started a new project and reproduce all as equal (name, bundleId, xcdatamodel etc) like in the old project.
I want to test if data are well saved when I update the old app with the new one, but when I build it, I get this error from xcode "application Permission denied".
I read that this error is due to the fact that I try to install an application with the same bundleId that already present on the device. I do not understand because I'm trying to simulate an update.
What can I do to get this working ?
Ok, here is the solution that helps me.
Build an archive an install it using iTunes instead of xcode direct build.
The other problem was, the first version of the app was built for the first iPad when xcode was available, but the devices were not yet sold. Here is the old -(NSPersistentStoreCoordinator *)persistentStoreCoordinator method used :
-(NSPersistentStoreCoordinator *)storeCoorditator{
if(storeCoorditator){
return storeCoorditator;
}
storeCoorditator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self objectModel]];
NSError *lc_error = nil;
NSString *lc_path = [NSString stringWithFormat:#"%#/data.sqlite",[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]];
NSURL *storeUrl = [NSURL fileURLWithPath:lc_path];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
[storeCoorditator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&lc_error];
return storeCoorditator;
}
And here is the method I tried to use :
-(NSPersistentStoreCoordinator *)persistentStoreCoordinator{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeUrl = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"PDF.sqlite"];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
As the storeUrl wasn't the same, the data couldn't be found ;)
Now everything's fine, I can get my data back !
Short question:
I want to run a certain code in my app only if my Core Data model has changed (new entities, new properties, etc). How can I determine if the model has changed or not?
Just some pseudo-code:
if (current_model_version != previous_model_version) {
//do some code
} else {
// do some other code
}
I'm guessing I might use versionHashes to do this, or isConfiguration:compatibleWithStoreMetadata:, but I'm not certain how.
Some editing for clarity: 'current' as in 'now' and 'previous' as in 'last time app was launched.'
The answer seems to be isConfiguration:compatibleWithStoreMedia:.
I found some useful information here:
http://mipostel.com/index.php/home/70-core-data-migration-standard-migration-part-2
I set it up this way:
- (BOOL)modelChanged
{
NSError *error;
NSURL * sourceURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"db.sqlite"];
NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType URL:sourceURL error:&error];
BOOL isCompatible = [[self managedObjectModel] isConfiguration:nil compatibleWithStoreMetadata:sourceMetadata];
return isCompatible;
}
'self' is my shared data store, not that it necessarily has to go there.
deanWombourne points out that what this really does is determine whether or not the data can be automatically migrated, so it's not exactly the solution to the problem I posed. It does serve my needs in this case.
This is replacement code for - (NSPersistentStoreCoordinator *)persistentStoreCoordinator that you get if you tick the Core Data box when setting up a new project in XCode.
It attempts to open the existing sqlite file (using lightweight migration if necessary). If that fails, it deletes and re-creates the store.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSError *error = nil;
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:self.storeURL options:options error:&error])
{
NSLog(#"Couldn't open the store. error %#, %#", error, [error userInfo]);
[self deleteSqliteFilesForStore:self.storeURL];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:self.storeURL options:options error:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
// or [NSException raise ...]
}
else
{
NSLog(#"Store deleted and recreated");
// TEST DATA RE-INITIALIZATION CODE GOES HERE
}
}
else
{
NSLog(#"Existing Store opened successfully");
}
return _persistentStoreCoordinator;
}
- (void)deleteSqliteFilesForStore:(NSURL *)storeURL
{
NSURL *baseURL = [storeURL URLByDeletingPathExtension];
// Delete the associated files as well as the sqlite file
for (NSString *pathExtension in #[#"sqlite",#"sqlite-shm",#"sqlite-wal"])
{
NSURL *componentURL = [baseURL URLByAppendingPathExtension:pathExtension];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:[componentURL path]];
if(fileExists)
{
[[NSFileManager defaultManager] removeItemAtPath:[componentURL path] error:nil];
}
}
}