Remove Core Data from iCloud fails - ios

I'm trying to remove Core Data from iCloud using [NSPersistentStoreCoordinator removeUbiquitousContentAndPersistentStoreAtURL:options:error:].
But I get strange output:
__93+[NSPersistentStoreCoordinator removeUbiquitousContentAndPersistentStoreAtURL:options:error:]_block_invoke(1982):
CoreData: Ubiquity: Unable to move content directory to new location:
file:///private/var/mobile/Library/Mobile%20Documents/<UBIQUITY_ID>/
New: file:///private/var/mobile/Library/Mobile%20Documents/OldUbiquitousContent-mobile~C9439AD0-1E87-4977-9C68-0674F5E2E93B
Error Domain=NSCocoaErrorDomain Code=513 "The operation couldn’t be completed.
(Cocoa error 513.)" UserInfo=0x181ab790 {NSSourceFilePathErrorKey=/private/var/mobile/Library/Mobile Documents/<UBIQUITY_ID>,
NSUserStringVariant=(
Move
), NSFilePath=/private/var/mobile/Library/Mobile Documents/<UBIQUITY_ID>,
NSDestinationFilePath=/private/var/mobile/Library/Mobile Documents/OldUbiquitousContent-mobile~C9439AD0-1E87-4977-9C68-0674F5E2E93B,
NSUnderlyingError=0x181aab50 "The operation couldn’t be completed. Operation not permitted"}
What does it mean?
How to avoid it? I'm working on iCloud disable/enable feature. Details HERE
UPDATE:
NSDictionary *iCloudOptions =
[NSDictionary dictionaryWithObjectsAndKeys:kICloudContentNameKey, NSPersistentStoreUbiquitousContentNameKey,
iCloudURL, NSPersistentStoreUbiquitousContentURLKey, nil];
// self.lastICloudStoreURL stores NSPersistentStore.URL after stack setup
BOOL result = [NSPersistentStoreCoordinator removeUbiquitousContentAndPersistentStoreAtURL:self.lastICloudStoreURL
options:iCloudOptions
error:&error];

Normally (before iOS7), you take the ubiquitousContentURL value from [fileManager URLForUbiquityContainerIdentifier:nil];
and pass it as an option called NSPersistentStore UbiquitousContentURLKey, and this is how iCloud know where to keep all of your data in the iCloud account.
In iOS 7 and Mac OS X we don't need to pass a value for that at all, and Apple call URLForUbiquitous ContainerIdentifier automatically under the hood for you.
So the solution looks like this:
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:kICloudContentNameKey, NSPersistentStoreUbiquitousContentNameKey, nil];
NSURL *storeURL = [NSPersistentStore MR_urlForStoreName:[MagicalRecord defaultStoreName]];
BOOL result = [NSPersistentStoreCoordinator removeUbiquitousContentAndPersistentStoreAtURL:storeURL options:options error:&error];
I suggest you to check WWDC 2013 session 207 to get this things clearly.

NSCocoaErrorDomain error 513 is defined in FoundationErrors.h as NSFileWriteNoPermissionError. You don't have the required permissions to write to the location.
This can happen if a managed object context is still using objects that are backed by this store. The context is actively using the file that is being moved, which results in a NSFileCoordinator conflict. Two things are trying to access the file with write permission at the same time.

The removeUbiquitousContentAndPersistentStoreAtURL:options:error: method deletes all of the user's local and cloud data—this is probably not what you want. Instead, migrate your store to a new location on disk and use the NSPersistentStoreRemoveUbiquitousMetadataOption option with the migratePersistentStore:toURL:options:withType:error: method.
See Disabling iCloud Persistence at Removing an iCloud-enabled Persistent Store in the iCloud Programming Guide for Core Data.

Related

iCloud Core Data "no document at URL"

I have an app with core data and icloud to allow the user to use the same database on several devices. The sync works most of the time perfect, but sometimes it doesn't, i.e. some transactions are simply skipped.
When I check the console, I get the error message:
__45-[PFUbiquityFilePresenter processPendingURLs]_block_invoke(439): CoreData: Ubiquity:  Librarian returned a serious error for starting downloads Error Domain=BRCloudDocsErrorDomain Code=5 "The operation couldn’t be completed. (BRCloudDocsErrorDomain error 5 - No document at URL)"
 
Funny enough this message appears even when the sync works.
 
The code fits to Apple latest version of " iCloud Programming Guide for Core Data".
The storeURL is coded as follows:
NSURL *documentsDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *storeURL = [documentsDirectory URLByAppendingPathComponent:#"iCloudSample.sqlite"];
The code for the options:
storeOptions = #{NSPersistentStoreUbiquitousContentNameKey: #"iCloudSampleStore" ,
NSPersistentStoreUbiquitousContentURLKey: #"iCloudSampleURL"};
The code for the store:
NSPersistentStore *store = [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:storeOptions
error:&error];
The merge policy (MOC is on the main qeue)
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
_managedObjectContext.mergePolicy = [[NSMergePolicy alloc]
initWithMergeType:NSMergeByPropertyObjectTrumpMergePolicyType];
As I understand the docs, there is not really much more to do(?).
But I guess I'm missing something, does anyone has an idea what to check, change, try?
Anyone who had the same problem? Any ideas are appreciated!
I have the same problem and solved it by removing the persistentStore and adding it again while the app is running. It is always the same problem when doing several changes in the first device, the second device doesn't get all the changes. I use the following code when applicationDidBecomeActive to remove the persistent store first:
var storeCoordinator:NSPersistentStoreCoordinator = persistentStoreCoordinator!
var store:NSPersistentStore = storeCoordinator.persistentStores[0] as! NSPersistentStore
var storeURL:NSURL = store.URL!
storeCoordinator.removePersistentStore(store, error: nil)
NSFileManager.defaultManager().removeItemAtPath(storeURL.path!, error: nil)
Following this I proceed to add the persistentStore again. Then I received all the notifications as it was the first run after installation but with the full data synched. it always works but I don't know if it is the proper way to handle the problem.
I believe that all the changes made in the first device always reach the second device but for some weird reason they don't merge completely with the local core data. I have made some weird observations about the iCloud-CoreData issue in:
https://stackoverflow.com/questions/32084952/core-data-to-icloud-doesnt-sync-all-changes
I hope it helps.
I found that when my store URL had .s in the path
(e.g. ---/com.companyName.project/---)
let applicationSupportDirectory = applicationSupportDirectoryURL.URLByAppendingPathComponent("com.companyName.project")
let iCloudStoreFileName = "iCloud.sqlite"
lazy var iCloudStoreURL: NSURL = {
return self.applicationSupportDirectory.URLByAppendingPathComponent(self.iCloudStoreFileName)
}()
When I enabled logging, by going to Edit Scheme... in Xcode for my project and adding:
-com.apple.CoreData.SyntaxColoredLogging 1
the log file in the debug window showed the path it was searching was
---/com~companyName~project/---
Core data had changed the .s to ~s. I eliminated the entire com.companyName.project from the URL and synchronizing seems to work better. But with core data and iCloud you never know - there's soooo much magic going on behind the scenes.

Save part of Core Data into the cloud

Working on iOS 7, I have to debug an application using Core Data with several entities. First I was asking for adding iCloud (and not cloud kit) to save all the data. But then, the client realized he wanted to save only some entities but not all of them into the cloud.
Is it something possible ? Do I need to use several NSPersistentStoreCoordinator ? (the application already use several NSManagedObjectContext, one per entity). Or maybe I can do something when I receive the notification :
NSPersistentStoreDidImportUbiquitousContentChangesNotification
and manually perform the merge but I really don't know how.
Thanks for your help.
Thanks to Tom Harrington, I created 2 configurations: CloudConfiguration and LocalConfiguration and I add some entities in each (this link helps me too).
Then, I add persistent store in the coordinator:
// Configure persistentStoreCoordinator
NSError* error1 = nil;
NSString *cloudConfiguration = #"CloudConfiguration";
NSPersistentStore *store1 = [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:#"CloudConfiguration"
URL:[self storeURLForConfiguration:cloudConfiguration]
options:#{ NSPersistentStoreUbiquitousContentNameKey : #"iCloudStore" }
error:&error1];
if (error1) {
NSLog(#"Error: %# \n Description : %# \nUser info : %#", error1, error1.description, error1.userInfo);
}
NSLog(#"*************** cloud store url ************** : %#", store1.URL);
NSError* error2 = nil;
NSString *localConfiguration = #"LocalConfiguration";
NSPersistentStore *localStore = [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:localConfiguration
URL:[self storeURLForConfiguration:localConfiguration]
options:nil
error:&error2];
if (error2) {
NSLog(#"Error: %# \n Description : %# \nUser info : %#", error2, error2.description, error2.userInfo);
}
NSLog(#"*************** local store url ************** : %#", localStore.URL);
All of my entities goes in one store only (cloud or local). Entities in different store don't have any relationship and I have a single model.
So here I go, I start my app for the first time all seems to be well configured. But when I try on another device or on the same device after deleting the app, I have a crash, just after getting : Using local storage: 0.
Here is the crash log :
[_PFUbiquityRecordImportOperation main](734): CoreData: Ubiquity: Error importing transaction log: <PFUbiquityTransactionLog: 0x16ed7f50>
transactionLogLocation: <PFUbiquityLocation: 0x16ed7ee0>: /var/mobile/Library/Mobile Documents/6ULEJ9RYTQ~fr~company~iCloudTestApp/CoreData/iCloudStore/mobile~E722813A-96E8-4E11-8DDE-56FF3837DEBD/iCloudStore/EU31J4aJIvvEyVMcWWYs1qgVajMk4_4fQxw1oe_Q0i0=/4C6B58B3-6C8D-4393-9B1E-8E48C7352091.1.cdt
transactionNumber: 1, exception: Invalid parameter value (bad entity)
User Info: (null)
2014-12-02 12:35:34.837 iCloudTestApp[1421:3b0b] -[_PFUbiquityRecordsImporter discoverAndImportAllAvailableLogs:error:](727): CoreData: Ubiquity: Exception while scanning for logs to import: Invalid parameter value (bad entity)
userInfo: (null)
It sounds weird to me because it's happen before the merge. Of course, I remove all the datas in the cloud before testing with those two configurations. If you have any idea...
Not multiple persistent store coordinators, but multiple persistent store files. You can add multiple persistent stores to the same coordinator by calling addPersistentStoreWithType:configuration:URL:options:error: multiple times. You don't have to use the same options every time, so you can use iCloud options for one store file but leave them out for a different one.
But you need to be aware of a couple of things:
You can't create relationships between objects in different persistent store files. If that's a problem, look into fetched properties. They're properties that transparently fetch objects using a predicate that you provide. They work sort of like one-way relationships.
You need some way to tell Core Data which store file to use for new objects. There are a couple of ways to do this:
If some of your entities could go in either persistent store, you'll need to use [NSManagedObjectContext assignObject:toPersistentStore:] every time you create a new instance.
For entities that will always be in the same store file, look into "configurations" for your model file. This lets you create named subsets of your model that contain only some of the entities. Use the configuration name when you add the persistent store file. Then, all new instances of those entities will automatically go to the right file.
Additional, based on updated question:
If you already have iCloud data that you need to use, you can't just switch to using configurations. The existing iCloud transaction logs may contain references to entities that are not in your new iCloud-only configuration. When it tries to import that data, it will fail, and you'll get errors like the one you're seeing.
If this app is still in development, I'd say just delete all existing iCloud data and go with the configuration. If you need to keep the existing iCloud data, you must ensure that all entities in the current iCloud data are still available. That most likely means you'll have to do without configurations and instead assign objects to one store or the other in your code.

+metadataForPersistentStoreOfType failure with NSSQLiteErrorDomain 14

I need to find the version of the model used in an existing persistent store so I can do a little post-processing after an automatic lightweight migration.
We've got a few devices in a state where we are unable to get the metadata for their persistent stores.
When we initialize the CoreData stack, we use NSPersistentStoreCoordinator's +metadataForPersistentStoreOfType:URL:error: method to get the metadata for the existing persistent store. Then we check if it's metadata is compatible with the metadata from the current object model to decide if we need to do a migration. We also pull the model versions from these two sets of metadata--so after the automatic lightweight migration we can do some simple post-processing based on the model versions.
The problem is that +metadataForPersistentStoreOfType:URL:error: is failing and returning nil on some devices. The error indicates NSSQLiteErrorDomain 14 and "I/O error for database at ". If I turn on SQLite debugging ("-com.apple.CoreData.SQLDebug 1") it doesn't produce much useful info:
CoreData: annotation: Connecting to sqlite database file at "/var/mobile/Applications/65838AB8-2DE4-4B1E-9837-FD252104448B/Library/slide_deck_database_1/StoreContent/persistentStore"
CoreData: sql: SELECT Z_VERSION, Z_UUID, Z_PLIST FROM Z_METADATA
CoreData: annotation: Disconnecting from sqlite database due to an error.
CoreData: error: (14) I/O error for database at /var/mobile/Applications/65838AB8-2DE4-4B1E-9837-FD252104448B/Library/slide_deck_database_1/StoreContent/persistentStore. SQLite error code:14, 'unable to open database file'
CoreData: annotation: Disconnecting from sqlite database.
metadataForPersistentStoreOfType failed with error: The operation couldn’t be completed. (Cocoa error 256.) (userInfo: {
NSSQLiteErrorDomain = 14;
NSUnderlyingException = "I/O error for database at /var/mobile/Applications/65838AB8-2DE4-4B1E-9837-FD252104448B/Library/slide_deck_database_1/StoreContent/persistentStore. SQLite error code:14, 'unable to open database file'";
})
I did find this article on Apple's website, which suggests a potential source of the issue: https://developer.apple.com/library/ios/qa/qa1809/_index.html But it doesn't seem to help with my problem. (It describes a way to migrate a persistent store and change the journaling options... but it assumes I already have an instance of NSPersistentStore, which I don't. I could add it via the PSC... but that would require migrating it, which defeats the whole purpose--again, because I'm trying to find the version of the existing store before I migrate it.)
One potential complication here is that I'm trying to switch from a UIManagedDocument store to a traditional CoreData stack (we're ditching UIManagedDocument because of a never-ending string of problems, and we understand a standard CoreData stack much better). In practice this hasn't seemed to have been a problem (until now?) but I thought it might be worth mentioning.
Edit: this is on an iPad (3rd generation) running iOS 7.1.1.
If you think the WAL vs. rollback thing is your problem, do the following:
Add the store to an NSPersistentStoreCoordinator using the rollback options as outlined in Technical Q&A 1809. If it can't do so for some other reason, you will get NO as the result of addPersistentStoreWithType:configuration:URL:options and a populated error.
To read the metadata, get the persistent store you just instantiated from the persistent store coordinator using persistentStoreForURL: .
At that point you can read the metadata for the store.
Example:
NSPersistentStore *store = nil;
NSError *error = nil;
NSDictionary *options = #{NSSQLitePragmasOption:#{#"journal_mode":#"DELETE"}};
if (! [persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:[self storeURL]
options:options
error:&error]) {
[self presentError:error];
}
store = [persistentStoreCoordinator persistentStoreForURL:[self storeURL]];
// read something out of the metadata.
id metaDataValue = [[store metadata] valueForKey: NSStoreUUIDKey];
You don't need to migrate it. You don't even need to keep that persistent store coordinator around. You can dispose of it as soon as you're done reading the metadata values.
Again, this somewhat assumes that your problem is the WAL vs. rollback SQLite store that is described in QA1809. Given the error that you are seeing any number of things could be the cause. For example, the store could have been previously corrupted by a borked write operation.

NSPersistentStoreCoordinator - how to handle schema incompatibility errors?

Every time I change the Core Data model for my app, it generates an unrecoverable error at the next startup: "The model used to open the store is incompatible with the one used to create the store".
The only reliable way to avoid this I have found is to manually delete the app and let Xcode reinstall it, or use other techniques to manually blow away the Core Data store .sqlite store file. This is obviously not viable for shipping out to users.
Apple's default App Delegate template for initializing the NSPersistentStoreCoordinator includes this comment:
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
/*
TODO: Replace this implementation with code to handle the error appropriately.
...
If you encounter schema incompatibility errors during development, you can reduce their frequency by:
* Simply deleting the existing store:
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
* Performing automatic lightweight migration by passing the following dictionary as the options parameter:
[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
*/
I have not been able to find any sample or instructions on how to "handle the error appropriately" though.
I would be happy to simply blow away the database in this case and let the app regenerate it from online data. This is what I do when I blow the database away manually. But how can I do that automatically in response to this error condition?
Are there any good samples explaining best practices?
Best way is to use lightweight migration. See Apple documentation: http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreDataVersioning/Articles/vmLightweightMigration.html.
When you need to make changes in model in new app version, you create new model. You do this in Xcode - just select Your current model and select from menu Editor/Add model version… Without this, automatic migration will not work.

iPhone Core Data "Automatic Lightweight Migration"

I am attempting to update an app that implements a core data store. I am adding an attribute to one of the entities.
I added the following code to my delegate class:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"Shoppee.sqlite"]];
NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
NSLog(#"Error: %#",error);
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return persistentStoreCoordinator;
}
This was from the following URL:
Doc
I get the following error when executing the code:
2009-12-01 20:04:22.877
Shoppee[25633:207] Error: Error
Domain=NSCocoaErrorDomain Code=134130
UserInfo=0x1624d20 "Operation could not be completed. (Cocoa error
134130.)" 2009-12-01 20:04:22.879 Shoppee[25633:207] Unresolved error
Error Domain=NSCocoaErrorDomain Code=134130 UserInfo=0x1624d20
"Operation could not be completed. (Cocoa error 134130.)", {
URL = file://localhost/Users/Eric/Library/Application%20Support/iPhone%20Simulator/User/Applications/A8A8FB73-9AB9-4EB7-8F83-82F5B4467AF1/Documents/MyApp.sqlite;
metadata = {
NSPersistenceFrameworkVersion = 241;
NSStoreModelVersionHashes = {
Item = <869d4b20 088e5c44 5c345006 87d245cd 67ab9bc4 14cadf45
180251e9 f741a98f>;
Store = <47c250f4 895e6fd1 5033ab42 22d2d493 7819ba75 3c0acffc
2dc54515 8deeed7a>;
};
NSStoreModelVersionHashesVersion = 3;
NSStoreModelVersionIdentifiers = (
);
NSStoreType = SQLite;
NSStoreUUID = "8DC65301-3BC5-42BE-80B8-E44577B8F8E1";
};
reason = "Can't find model for source store"; }
It looks like I somehow need to include the original data model but I am not sure how to do that. Any suggestions?
To recap/Full guide:
Before making any change, create a new model version.
In Xcode 4: Select your .xcdatamodel -> Editor -> Add Model Version.
In Xcode 3: Design -> Data Model -> Add Model Version.
You will see that a new .xcdatamodel is created in your .xcdatamodeld folder (which is also created if you have none).
Save.
Select your new .xcdatamodel and make the change you wish to employ in accordance with the Lightweight Migration documentation.
Save.
Set the current/active schema to the newly created schema.
With the .xcdatamodeld folder selected:
In Xcode 4: Utilities sidebar -> File Inspector -> Versioned Core Data Model -> Select the new schema.
In Xcode 3: Design > Data Model > Set Current Version.
The green tick on the .xcdatamodel icon will move to the new schema.
Save.
Implement the necessary code to perform migration at runtime.
Where your NSPersistentStoreCoordinator is created (usually AppDelegate class), for the options parameter, replace nil with the following code:
[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil]
Run your app. If there's no crash, you've probably successfully migrated :)
When you have successfully migrated, the migration code (step 7) can be removed. (It is up to the developer to determine when the users of a published app can be deemed to have migrated.)
IMPORTANT: Do not delete old model versions/schemas. Core Data needs the old version to migrate to the new version.
I figured it out.
Design > Data Model > Add Model Version
For Googlers again, this is what you need to do (assuming you have already set up Lightweight Migration):
Before making changes, Do Design -> Data Model -> Add Model Version (you will see that a new .xcdatamodel is created in your .xcdatamodeld folder)
Save
Make your change
Save
Run App
Step #1 is crucial for making this work. I ran into this problem because I had followed these steps to add a new field. That worked. I added a second new field, but forgot to "Add Model Version", and things blew up.
Also for googlers.. Simple rule, never delete/edit any old numbered version. When you Add Model Version the number suffix will increase as 2..3..4 meaning 2 is the oldest 3 next etc.. but the Current one to edit is the unnumbered version.
Do not delete old model versions as users with previous db using an old model version will not be able to migrate to your latest db model with out comparing old and latest schemas.
Just a note for those that come across this Googling, it seems even with auto(magic) migration you still need to create a version of your original store, and a new one, and set the new one as the current version.
So far I only see how to avoid the error message.
But how do we fix it - in case we messed things up already??
The following solution fixed the problem but you will loose the data in the DB:
Delete / rename the sqlite file of the deployed / installed application.
The files name an location are given directly after the error message. e.g.:
reason=Can't find model for source store}, {
URL = "file://localhost/Users/yourName/Library/Application%20Support/iPhone%20Simulator/4.3/Applications/62F342D4-F007-4F6F-96D2-68F902D3719A/Documents/Locations.sqlite";
Something to keep in mind when doing a lightweight migration -
If you plan to rename/modify attributes, remember to set the "Renaming ID" value in either the new or the old model. To use Apple's own example, in XCode 4.3, select paintColor in the new model > switch to the Data Model Inspector > Set the "Renaming ID" field to Color in the "Versioning" section. For me, failure to do this step led to a run time error. This same error is also covered here. As a new user, I'm not allowed to post images, so here's an imgur link (not spam, really).
(Cocoa error 134140.)" UserInfo=0x622b350 {reason=Can't find or automatically infer mapping model for migration
You can also get this error when making a change to the data model and running on an installed app that has a different version of the sqlite file. In this case just delete the installed app and re-run it.
Just in case someone runs into this scenario and none of the above works... I was deleting my app from the simulator, cleaning, etc, but nothing would work. I had to go to the simulator directory and manually rm the .sqlite file to get the app working again. No clue...

Resources