Consider the following simple entity model: Entity A has a to-one relationship to Entity B called b. Entity B has an inverse to-one relationship called a. Neither relationship is optional.
A B
b < ----- > a
Assume we have two devices (1) and (2) that begin fully synced. Each has one object of class A, and one object of class B, and they are associated with one another. On device 1, we have objects A1 and B1, and on device B we have the same logical objects A1 and B1.
Now assume that simulateous changes are made on each device:
On device 1, we delete B1, insert B2, and associate A1 with B2. Then save changes.
On device 2, we delete B1, insert B3, and associate A1 with B3. Then save changes.
Device 1 now attempts to import the transaction logs from device 2. B3 will be inserted, and A1 will be associated with B3. So far so good, but B2 is now left with relationship a equal to nil. The a relationship is non-optional, so a validation error occurs.
Something similar will occur on device 2, because there are two B objects, and only one A object to be associated with. There must thus always be a validation error, because one of B objects must have an a relationship set to nil.
Even worse, any future change will always leave an errant B object hanging around, and so will fail validation. In effect, the user cannot fix the problem themselves by resetting the relationship. It is permanently broken.
The question is, how can you address a validation error like this? This all happens before the NSPersistentStoreDidImportUbiquitousContentChangesNotification
notification is triggered. It is not a validation error in your apps main NSManagedObjectContext
, it is a validation error that occurs during the initial import of transaction logs into the persistent store.
The only option I can think of is perhaps to try to delete the invalid B object in a custom setter (setA:
), or in a KVC validation method (validateA:error:
) on the B class itself, because these do seem to be triggered during the transaction log import. But I'm not sure that side effects like these are allowed, and when I try it, it does seem to result in nasty log messages to that effect.
Anyone know what the right way to handle this is?
Use Core Data with CloudKit to give users seamless access to the data in your app across all their devices. Core Data with CloudKit combines the benefits of local persistence with cloud backup and distribution. Core Data provides powerful object graph management features for developing an app with structured data.
Rules can be set up in the dashboard: CloudKit has a lot of tabs. Summary: Firebase can be immediately accessed without authentication but also provides the flexibility to define your own rules. CloudKit is probably more secure and can provide a seamless experience for your users too, but only if they are using iOS.
Use Core Data to save your application's permanent data for offline use, to cache temporary data, and to add undo functionality to your app on a single device. To sync data across multiple devices in a single iCloud account, Core Data automatically mirrors your schema to a CloudKit container.
Core Data is a framework that you use to manage the model layer objects in your application. It provides generalized and automated solutions to common tasks associated with object life cycle and object graph management, including persistence.
Check out this thread on Apple's Developer Forums:
https://devforums.apple.com/message/641930#641930
There is a response from an Apple employee. In a nutshell:
1) This is a known bug in Core Data iCloud syncing under current versions of iOS (5.1) and OS X (10.7.3).
2) If the relationship is non-optional with a validation predicate, syncing will cease completely. So you will need to remove the validation for the time-being to keep things flowing. However, doing so will leave the devices with mismatched data.
3) There is no official workaround for this. One approach, which is messy, would be to maintain a separate attribute which tracks the relationship. Then, you would need to scan through any objects changed via iCloud and fix the relationship if it is nil.
I am also bitten by this bug. Very frustrating to deal with customers who run into troubles. Hopefully a fix from Apple with be out soon...
In case this helps others, I have made a bit more progress working around this bug in iCloud. What I've done is disabled validation during the initial transaction log import, and enabled it again when my MOC goes to save to my main store. I still get validation errors, of course, but they happen in my MOC save method, rather than deep in the Core Data framework, so I can repair the errors (e.g. delete invalid objects).
How do I 'disable' validation? The way I am doing it is to override the KVC validation method in my NSManagedObject subclass, which I have used as the root class for all Core Data entity classes.
-(BOOL)validateValue:(__autoreleasing id *)value forKey:(NSString *)key error:(NSError *__autoreleasing *)error
{
if ( ![self.managedObjectContext isKindOfClass:[MCManagedObjectContext class]] ) {
return YES;
}
else {
return [super validateValue:value forKey:key error:error];
}
}
In the override, I check the class of the managed object context, and if it is my custom class, I do the validation normally by chaining to the super method. If it is not my custom subclass, I return YES to indicate validity.
Of course, you could make this more nuanced, but you hopefully get the idea: you try to determine whether this is one of your app's standard saves or a iCloud import save, and branch accordingly.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With