我的解决方案是将数据集的两个副本存储在CoreData数据库中。一个表示最后已知的服务器状态,并且是不可变的。另一个由用户编辑。
当需要同步更改时,应用程序会创建所编辑和不可变副本之间的差异。应用程序将差异发送到Web服务,该服务将差异应用于其自己的数据副本。它会回复一个完整的数据集副本,而应用程序会将其覆盖到数据的两个副本上。
这样做的好处是:
- 如果没有网络连接,则不会丢失任何更改:每次需要发送数据集时计算差异,而只有在成功同步时才会更改不可变副本。
- 只传输需要发送的最小信息量。
- 多人可以同时编辑相同的数据,而无需使用锁定策略,最小化通过覆盖造成的数据丢失的机会。
这样做的缺点是:
- 编写差异代码很复杂。
- 编写合并服务也很复杂。
- 除非你是元编程大师,否则你会发现你的差异/合并代码很脆弱,并且必须在更改对象模型时进行更改。
我考虑了以下一些因素:
- 如果允许离线进行更改,则无法使用签入/签出锁定(如何在没有连接的情况下建立锁定?)。
- 如果两个人同时编辑相同的数据会发生什么?
- 如果一个人在一台iOS设备上无法连接时编辑数据,然后关闭设备,在另一台设备上编辑,然后再打开原始设备会发生什么?
- 使用CoreData进行多线程处理是一个完整的问题类。
我听说过类似于iOS6中的新iCloud/CoreData同步系统的开箱即用支持做任何远程操作的最接近的东西,该系统在实体从CoreData数据库更改时自动将其传输到iCloud。但是,这意味着你必须使用iCloud。
编辑:这很晚了,我知道,但是这里有一个能够在两个NSManagedObject实例之间生成差异的类。
@interface SZManagedObjectDiff
- (NSDictionary *)diffNewObject:(NSManagedObject *)newObject withOldObject:(NSManagedObject *)oldObject
@end
#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];
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];
}
}
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
关于它的功能、实现方式以及限制/假设的更多信息,请访问以下链接:
http://simianzombie.com/?p=2379