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.
Related
What is the recommended place to save game data if I do not want automatically to be backup to iCloud ? I am using NSUbiquitousKeyValueStore for iCloud, and have custom logic for updates.
So I do not for iCloud backup to mess things up.
By my understanding that is: Library/Caches/.
Because: Documents/ and Library/Preferences/ are backup to iCloud.
Am I correct ?
You shouldn't use /Library/Cache for storing such kind of information. Look what Apple Data Storage Guidelines says:
Data that can be downloaded again or regenerated should be stored in
the /Library/Caches directory.
You can save all your data to /Documents, just flag it so that it wouldn't copy to iCloud:
- (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;
}
You can use this method in your AppDelegate or wherever you create these files. Note that you can exlude from iCloud backup not only specific files, but directories too.
You can also exclude the specific game data from performing backup to iCloud.
NSURL *url = [NSURL fileURLWithPath:fileName];
assert([[NSFileManager defaultManager] fileExistsAtPath:fileName]);
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;
I need to create two persistent stores, each with their own entities, with one persistent store coordinator. The hard part is, I want one persistent store to be linked to iCloud, but the other to only be a local store. I have read about making different configurations for the managed object model, but how do I fetch entities from the local store, rather than the iCloud enabled store? Here is my code so far, Am I headed in the right direction?:
NSMutableDictionary *options = [NSMutableDictionary dictionary];
[options setValue:[NSNumber numberWithBool:YES] forKey:NSMigratePersistentStoresAutomaticallyOption];
[options setValue:[NSNumber numberWithBool:YES] forKey:NSInferMappingModelAutomaticallyOption];
NSURL *cloudURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
if (cloudURL)
{
NSLog(#"iCloud enabled: %#", cloudURL);
cloudURL = [cloudURL URLByAppendingPathComponent:#"FSListen"];
[options setValue:kICloudContentNameKey forKey:NSPersistentStoreUbiquitousContentNameKey];
[options setValue:cloudURL forKey:NSPersistentStoreUbiquitousContentURLKey];
}
else
{
NSLog(#"iCloud is not enabled");
}
// create the persistent store that will be connected to iCloud for favorites
NSURL *iClouldSoreURL= [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
iClouldSoreURL = [iClouldSoreURL URLByAppendingPathComponent:#"FSListen-iCloud.sqlite"];
NSError *error = nil;
NSPersistentStoreCoordinator *coordinator = [self.managedObjectContext persistentStoreCoordinator];
NSPersistentStore *store = [coordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:#"Default"
URL:iClouldSoreURL
options:options
error:&error];
if (!store)
{
NSLog(#"Error adding persistent store to coordinator %#\n%#", [error localizedDescription], [error userInfo]);
//Present a user facing error
}
// create the persistent store that will not be connected to iCloud for downloads
NSError *downloadsStoreError = nil;
NSURL *downloadsStoreURL = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
downloadsStoreURL = [downloadsStoreURL URLByAppendingPathComponent:#"FSListen-Downloads.sqlite"];
NSPersistentStore *downloadsStore = [coordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:#"Downloads"
URL:downloadsStoreURL
options:nil
error:&downloadsStoreError];
if (!downloadsStore)
{
NSLog(#"ERROR CREATING DOWNLOADS STORE %#", downloadsStoreError.localizedDescription);
}
In my managed object model, I have one configuration with an entity called 'downloads' that I only want to be saved locally, but it is also in the default configuration, which I want to link to iCloud. How do I make sure I am saving my entities in the correct configuration?
I am trying to migrate my current core data structure to a new data structure however I am continually getting the same error no matter what I do:
Terminating app due to uncaught exception 'Open Failed', reason: 'Reason: The operation couldn’t be completed. (Cocoa error 134100.)'
According to the documentation this means: NSPersistentStoreIncompatibleVersionHashError
I cannot just erase the app from the simulator as it has already gone out to the app store.
The persistent store coordinator in the app delegate is as follows:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"DATABASELOC.sqlite"];
NSLog(#"URL: %#", storeURL);
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;
}
And my managedobjectmodel within the app delegate is as follows:
- (NSManagedObjectModel *)managedObjectModel
{
if (managedObjectModel != nil) {
return managedObjectModel;
}
NSString *path = [[NSBundle mainBundle] pathForResource:#"DATABASELOC" ofType:#"momd"];
NSURL *momURL = [NSURL fileURLWithPath:path];
managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:momURL];
return managedObjectModel;
}
I have generated a second model version, and updated the current version, and if I choose either as the current, if it is the same as the original model it loads fine. However as soon as I make a minor change the model that is current the same error comes up.
I guess it appears that it is not recognizing that there is a new model, but no matter what I do I cannot get past this error.
Has anyone else had this problem? Can anyone offer some advice?
Let me know if you need any more information.
Thanks in advance for your help!
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];
}
}
}
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.