Mi solución a esto fue almacenar dos copias del conjunto de datos en una base de datos CoreData. Uno representa el último estado del servidor conocido y es inmutable. El otro es editado por el usuario.
Cuando llega el momento de sincronizar los cambios, la aplicación crea una diferencia entre las copias editadas e inmutables de los datos. La aplicación envía el diff a un servicio web que aplica el diff a su propia copia de los datos. Responde con una copia completa del conjunto de datos, que la aplicación sobrescribe en ambas copias de los datos.
Las ventajas son:
- Si no hay conectividad de red, no se pierden los cambios: el diff se calcula cada vez que el conjunto de datos tiene que ser enviado, y la copia inmutable sólo se modifique por una exitosa sincronizar
- Solo se transmite la cantidad mínima de información que se debe enviar.
- Varias personas pueden editar los mismos datos al mismo tiempo sin utilizar estrategias de bloqueo con una oportunidad mínima para la pérdida de datos mediante sobrescrituras.
Las desventajas son:
- escribir el código diffing es compleja.
- Escribir el servicio de fusión es complejo.
- A menos que sea un gurú de la metaprogramación, encontrará que su código diff/merge es frágil y tiene que cambiar cada vez que cambie su modelo de objeto.
Estas son algunas de las consideraciones que tenía cuando sube con la estrategia:
- Si permite que se realicen cambios sin conexión, cierre Entrada/Salida no funcionará (¿cómo se puede establecer una bloqueo sin conexión?).
- ¿Qué sucede si dos personas editan los mismos datos al mismo tiempo?
- ¿Qué sucede si una persona edita datos en un dispositivo iOS cuando no tiene conexión, lo apaga, lo edita en otro dispositivo y luego vuelve a encenderlo?
- El subprocesamiento múltiple con CoreData es una clase de problema completo en sí mismo.
Lo más parecido que he oído hablar de que el apoyo fuera de la caja para hacer algo remotamente parecido a esto es el nuevo sistema de sincronización iCloud/CoreData en iOS 6, que transmite automáticamente entidades de una base de datos CoreData a iCloud cuando cambian Sin embargo, eso significa que debes usar iCloud.
EDITAR: Esto es muy tarde, lo sé, pero aquí hay una clase que es capaz de producir una diferencia entre dos instancias de NSManagedObject.
// SZManagedObjectDiff.h
@interface SZManagedObjectDiff
- (NSDictionary *)diffNewObject:(NSManagedObject *)newObject withOldObject:(NSManagedObject *)oldObject
@end
// SZManagedObjectDiff.m
#import "SZManagedObjectDiff.h"
@implementation SZManagedObjectDiff
- (NSDictionary *)diffNewObject:(NSManagedObject *)newObject withOldObject:(NSManagedObject *)oldObject {
NSDictionary *attributeDiff = [self diffAttributesOfNewObject:newObject withOldObject:oldObject];
NSDictionary *relationshipsDiff = [self diffRelationshipsOfNewObject:newObject withOldObject:oldObject];
NSMutableDictionary *diff = [NSMutableDictionary dictionary];
if (attributeDiff.count > 0) {
diff[@"attributes"] = attributeDiff;
}
if (relationshipsDiff.count > 0) {
diff[@"relationships"] = relationshipsDiff;
}
if (diff.count > 0) {
diff[@"entityName"] = newObject ? newObject.entity.name : oldObject.entity.name;
NSString *idAttributeName = newObject ? newObject.entity.userInfo[@"id"] : oldObject.entity.userInfo[@"id"];
if (idAttributeName) {
id itemId = newObject ? [newObject valueForKey:idAttributeName] : [oldObject valueForKey:idAttributeName];
if (itemId) {
diff[idAttributeName] = itemId;
}
}
}
return diff;
}
- (NSDictionary *)diffRelationshipsOfNewObject:(NSManagedObject *)newObject withOldObject:(NSManagedObject *)oldObject {
NSMutableDictionary *diff = [NSMutableDictionary dictionary];
NSDictionary *relationships = newObject == nil ? [[oldObject entity] relationshipsByName] : [[newObject entity] relationshipsByName];
for (NSString *name in relationships) {
NSRelationshipDescription *relationship = relationships[name];
if (relationship.deleteRule != NSCascadeDeleteRule) continue;
SEL selector = NSSelectorFromString(name);
id newValue = nil;
id oldValue = nil;
if (newObject != nil && [newObject respondsToSelector:selector]) newValue = [newObject performSelector:selector];
if (oldObject != nil && [oldObject respondsToSelector:selector]) oldValue = [oldObject performSelector:selector];
if (relationship.isToMany) {
NSArray *changes = [self diffNewSet:newValue withOldSet:oldValue];
if (changes.count > 0) {
diff[name] = changes;
}
} else {
NSDictionary *relationshipDiff = [self diffNewObject:newValue withOldObject:oldValue];
if (relationshipDiff.count > 0) {
diff[name] = relationshipDiff;
}
}
}
return diff;
}
- (NSDictionary *)diffAttributesOfNewObject:(NSManagedObject *)newObject withOldObject:(NSManagedObject *)oldObject {
NSMutableDictionary *diff = [NSMutableDictionary dictionary];
NSArray *attributeNames = newObject == nil ? [[[oldObject entity] attributesByName] allKeys] : [[[newObject entity] attributesByName] allKeys];
for (NSString *name in attributeNames) {
SEL selector = NSSelectorFromString(name);
id newValue = nil;
id oldValue = nil;
if (newObject != nil && [newObject respondsToSelector:selector]) newValue = [newObject performSelector:selector];
if (oldObject != nil && [oldObject respondsToSelector:selector]) oldValue = [oldObject performSelector:selector];
newValue = newValue ? newValue : [NSNull null];
oldValue = oldValue ? oldValue : [NSNull null];
if (![newValue isEqual:oldValue]) {
diff[name] = @{ @"new": newValue, @"old": oldValue };
}
}
return diff;
}
- (NSArray *)diffNewSet:(NSSet *)newSet withOldSet:(NSSet *)oldSet {
NSMutableArray *changes = [NSMutableArray array];
// Find all items that have been newly created or updated.
for (NSManagedObject *newItem in newSet) {
NSString *idAttributeName = newItem.entity.userInfo[@"id"];
NSAssert(idAttributeName, @"Entities must have an id property set in their user info.");
id newItemId = [newItem valueForKey:idAttributeName];
NSManagedObject *oldItem = nil;
for (NSManagedObject *setItem in oldSet) {
id setItemId = [setItem valueForKey:idAttributeName];
if ([setItemId isEqual:newItemId]) {
oldItem = setItem;
break;
}
}
NSDictionary *diff = [self diffNewObject:newItem withOldObject:oldItem];
if (diff.count > 0) {
[changes addObject:diff];
}
}
// Find all items that have been deleted.
for (NSManagedObject *oldItem in oldSet) {
NSString *idAttributeName = oldItem.entity.userInfo[@"id"];
NSAssert(idAttributeName, @"Entities must have an id property set in their user info.");
id oldItemId = [oldItem valueForKey:idAttributeName];
NSManagedObject *newItem = nil;
for (NSManagedObject *setItem in newSet) {
id setItemId = [setItem valueForKey:idAttributeName];
if ([setItemId isEqual:oldItemId]) {
newItem = setItem;
break;
}
}
if (!newItem) {
NSDictionary *diff = [self diffNewObject:newItem withOldObject:oldItem];
if (diff.count > 0) {
[changes addObject:diff];
}
}
}
return changes;
}
@end
Hay más información sobre lo que hace, cómo lo hace y sus limitaciones/supuestos aquí:
http://simianzombie.com/?p=2379
Creo que la funcionalidad de sincronización de la que está hablando está disponible en IOS5 pero el show-stopper (como usted citó) es que se está sincronizando con iCloud y no con el sistema con el que debo interactuar (SQLServer). Me gusta mucho tu idea, pero me preocupan los problemas que señalaste: descifrar los deltas. ¿Tienes alguna experiencia con NSIncrementalStore? Todavía estoy confundido sobre lo que es. – JustLearningAgain
NSIncrementalStore es una clase base para implementar cualquier tipo de sistema de almacenamiento como una tienda CoreData. ¿Desea usar un archivo XML con la API CoreData? Heredar de NSIncrementalStore y escribir los métodos para hacerlo. No creo que te ayude con tu situación. – Ant
¿Me permite hacer ambas cosas? ¿Podría usarlo para actualizar los datos principales, así como mi servicio web externo? – JustLearningAgain