【问题标题】:Synchronize IOS core data with web service?将 IOS 核心数据与 Web 服务同步?
【发布时间】:2012-08-11 06:37:38
【问题描述】:

这是我的问题:

  • 我想使用核心数据 - 速度和连接问题来构建我的 IOS 应用程序。存储在核心数据中的数据来自 SQLServer 数据库,我可以通过尚未定义的 Web 服务访问该数据库。
  • 对存储在核心数据中的数据的任何更改都需要通过 Web 服务与 SQLServer 同步。此外,我需要缓冲由于连接问题而无法同步的更改。
  • 我还需要使用服务器上发生的任何更改来更新核心数据。这可能会按照用户偏好设置的时间表进行。

我探索过的解决方案:

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

我倾向于AFIncrementalStore,因为这是使用(似乎是)更标准的方法。问题是,我可能完全不了解NSIncrementalStore 的真正含义。

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

【问题讨论】:

    标签: web-services ios5 core-data


    【解决方案1】:

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

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

    优点是:

    • 如果没有网络连接,则不会丢失任何更改:每次需要发送数据集时都会计算 diff,只有在成功同步时才会更改不可变副本。
    • 只传输需要发送的最少量信息。
    • 多人可以同时编辑相同的数据,而无需使用锁定策略,从而最大限度地减少因覆盖而丢失数据的机会。

    缺点是:

    • 编写差异代码很复杂。
    • 编写合并服务很复杂。
    • 除非您是元编程大师,否则您会发现您的差异/合并代码很脆弱,并且必须在更改对象模型时进行更改。

    以下是我在制定策略时的一些考虑:

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

    我听说的最接近开箱即用支持的远程操作是 iOS6 中新的 iCloud/CoreData 同步系统,当实体发生变化时,它会自动将实体从 CoreData 数据库传输到 iCloud .但是,这意味着您必须使用 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

    【讨论】:

    • 我认为您所说的同步功能在 IOS5 中可用,但显示停止器(如您所引用的)是它与 iCloud 同步,而不是与我必须与之交互的系统(SQLServer)同步。我非常喜欢你的想法,但我担心你指出的问题:找出增量。你有 NSIncrementalStore 的经验吗?我仍然对它到底是什么感到困惑。
    • NSIncrementalStore 是一个基类,用于将任何类型的存储系统实现为 CoreData 存储。想要将 XML 文件与 CoreData API 一起使用?从 NSIncrementalStore 继承并编写方法来做到这一点。我认为这对您的情况没有帮助。
    • 它允许我两者都做吗?我可以用它来更新核心数据以及我的外部网络服务吗?
    • 您可以使用与 AFIncrementalStore 相同的方式,但您最好使用该类而不是滚动您自己的类。与 CoreData 和 Web API 交互是最简单的部分。您面临的真正挑战是处理连接状态与断开连接状态以及多个用户同时访问相同数据,这两者都需要您创建自己的自定义解决方案。
    • 我浏览了一些为 AFIncrementalStore 编写的示例代码。如果我理解它,它基本上允许程序员使用核心数据api从核心数据以外的其他来源获取数据。它通过子类化 AFIncrementalStore 覆盖一些调用来实现这一点。这似乎是一个全有或全无的冒险。它似乎实际上并没有在核心数据中存储任何内容。有没有简单的方法让它同时更新?
    【解决方案2】:

    使用 Parse 平台及其 IOS SDK 来构建和存储信息。它可以在本地缓存数据,因此您可以在没有连接时快速检索它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-04-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-18
      • 1970-01-01
      • 2023-03-25
      相关资源
      最近更新 更多