将IOS核心数据与Web服务同步?

问题描述:

这里是我的问题:将IOS核心数据与Web服务同步?

  • 我想使用的核心数据 - 速度和连接问题,以建立我的iOS应用。存储在核心数据中的数据来自SQLServer数据库,我可以通过一个尚未定义的Web服务访问该数据库。
  • 对存储在核心数据中的数据所做的任何更改都需要通过Web服务与SQLServer同步。另外,我需要缓存由于连接问题而不同步的更改。
  • 我还需要使用服务器上发生的任何更改来更新核心数据。这可能发生在用户偏好设置的时间表上。

解决方案我已经了解:

  • 使用NSIncrementalStore类(在iOS 5中新)。我很困惑这是什么,但听起来很有希望。从我所知道的,你可以子类NSIncrementalStore,它允许你拦截常规的核心数据API调用。然后,我可以将这些信息传递给核心数据,并通过Web服务将其与外部数据库同步。我可能完全错误。但假设我是对的,如果与互联网的连接断开,我将如何同步增量?
  • AFIncrementalStore - 这是NSIncrementalStore的子类,使用AFNetworking来完成网络服务。
  • RestKit - 我有点担心这个API的活跃程度,它似乎正在通过过渡到块功能。有没有人广泛使用过?

我倾向于AFIncrementalStore因为这是使用(似乎是)更标准的方法。问题是,我可能完全不知道NSIncrementalStore究竟是什么。

一些示例代码或教程的链接将会很棒!

我对此的解决方案是将数据集的两个副本存储在CoreData数据库中。一个代表最后一个已知的服务器状态,并且是不可变的。另一个是由用户编辑的。

当需要同步更改时,应用程序会在已编辑和不可变的数据副本之间创建差异。应用程序将diff发送到一个Web服务,该服务将diff应用于其自己的数据副本。它用数据集的完整副本进行回复,该应用程序将覆盖该数据的两个副本。

的优点是:

  • 如果没有网络连接,没有任何更改将丢失:中DIFF在每个数据集需要被发送的时间计算,并且不可改变的副本只改变了一个成功的同步。
  • 只发送需要发送的最小信息量。
  • 多人可以同时编辑相同的数据,而无需使用锁定策略,并通过覆盖来减少数据丢失的机会。

缺点是:

  • 编写代码版本比较复杂。
  • 编写合并服务很复杂。
  • 除非您是元编程专家,否则您会发现您的差异/合并代码很脆弱,并且每当您更改对象模型时都必须更改。

这里有一些上来时与策略,我有考虑的:

  • 如果允许更改离线制作,签入/签锁定将无法正常工作(你怎么能建立一个锁定没有连接?)。
  • 如果两个人同时编辑相同的数据会发生什么?
  • 如果一个人在无连接时在一台iOS设备上编辑数据,将其关闭,在另一台设备上编辑然后重新打开原始设备,会发生什么情况?
  • CoreData多线程本身就是一个完整的问题类。

我听说到外的现成支持做任何远程这样最接近的是iOS6的,它会自动从数据库CoreData到iCloud传送实体新的iCloud/CoreData同步系统当他们改变时。但是,这意味着您必须使用iCloud。

编辑:这是非常晚,我知道,但是这里有一个类能够产生两个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 

有一个关于它做什么,它是如何做它和它的局限性/假设这里的更多信息:

http://simianzombie.com/?p=2379

+0

我认为你正在讨论的同步功能可以在IOS5中找到,但是show-stopper(正如你所引用的)是它与iCloud同步,而不是我必须与之交互的系统(SQLServer)。我很喜欢你的想法,但我很关心你指出的问题:找出三角洲。你有使用NSIncrementalStore的经验吗?我仍然对它究竟是什么感到困惑。 – JustLearningAgain 2012-08-15 02:23:40

+0

NSIncrementalStore是用于将任何类型的存储系统实现为CoreData存储的基类。想用CoreData API使用XML文件吗?从NSIncrementalStore继承并编写方法来执行此操作。我认为这不会对你的情况有所帮助。 – Ant 2012-08-15 03:21:05

+0

它允许我做到两个吗?我可以用它来更新核心数据以及我的外部Web服务吗? – JustLearningAgain 2012-08-15 07:11:39

使用parse平台和IOS SDK构建和存储信息。它可以在本地缓存数据,以便在没有连接时快速检索数据。