如何将iOS核心数据与Web服务同步?

8

我的问题是:

  • 我想使用Core Data技术构建我的iOS应用程序,但速度和连接方面存在问题。存储在Core Data中的数据来自SQLServer数据库,我可以通过一个未定义的web服务访问它。
  • 对于在Core Data中存储的任何更改,都需要通过web服务与SQLServer进行同步。此外,由于连接问题,我需要缓冲那些无法同步的更改。
  • 我还需要将发生在服务器上的任何更改更新到Core Data中。这可能会按照用户偏好设置的计划进行。

我研究过的解决方案:

  • 使用NSIncrementalStore类(IOS 5中新增)。我非常困惑它的确切作用,但听起来很有前途。从我所知道的来看,您可以创建NSIncrementalStore子类,它允许您拦截常规的Core Data API调用。然后,我可以将信息传递给Core Data,并通过web服务将其与外部数据库同步。但如果因为网络连接问题无法同步增量数据,该怎么办?我可能完全错误。但是假设我是正确的,如果连网不通,如何同步增量数据?
  • AFIncrementalStore-这是NSIncrementalStore的一个子类,使用AFNetworking来处理web服务的部分。
  • RestKit-我有点担心这个API的活动度如何,而且它似乎正在经历向块功能转变的过程。是否有人广泛使用过它?

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

一些示例代码或教程的链接将非常有帮助!

2个回答

5
我的解决方案是将数据集的两个副本存储在CoreData数据库中。一个表示最后已知的服务器状态,并且是不可变的。另一个由用户编辑。
当需要同步更改时,应用程序会创建所编辑和不可变副本之间的差异。应用程序将差异发送到Web服务,该服务将差异应用于其自己的数据副本。它会回复一个完整的数据集副本,而应用程序会将其覆盖到数据的两个副本上。
这样做的好处是:
- 如果没有网络连接,则不会丢失任何更改:每次需要发送数据集时计算差异,而只有在成功同步时才会更改不可变副本。 - 只传输需要发送的最小信息量。 - 多人可以同时编辑相同的数据,而无需使用锁定策略,最小化通过覆盖造成的数据丢失的机会。
这样做的缺点是:
- 编写差异代码很复杂。 - 编写合并服务也很复杂。 - 除非你是元编程大师,否则你会发现你的差异/合并代码很脆弱,并且必须在更改对象模型时进行更改。
我考虑了以下一些因素:
- 如果允许离线进行更改,则无法使用签入/签出锁定(如何在没有连接的情况下建立锁定?)。 - 如果两个人同时编辑相同的数据会发生什么? - 如果一个人在一台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的任何经验吗?我仍然对它到底是什么感到困惑。 - JustLearningAgain
NSIncrementalStore是一个基类,用于将任何类型的存储系统实现为CoreData存储。想要使用XML文件与CoreData API吗?继承NSIncrementalStore并编写相应的方法即可。但我认为这对你的情况没有帮助。 - Ant
它允许我同时进行吗?我可以使用它来更新核心数据以及我的外部网络服务吗? - JustLearningAgain
你可以像AFIncrementalStore一样做,但最好使用那个类而不是自己编写。与CoreData和Web API交互是容易的部分。你真正面临的挑战是处理连接和断开状态以及多个用户同时访问相同数据,这两者都需要你创建自己的定制解决方案。 - Ant
我仔细阅读了一些为AFIncrementalStore编写的示例代码。 如果我理解正确,它基本上允许程序员使用核心数据API从除核心数据外的其他来源获取数据。 它通过通过子类化AFIncrementalStore覆盖一些调用来实现此目的。 这似乎是一项全面性的工作,它似乎没有在核心数据中实际存储任何内容。 有没有一种简单的方法可以同时更新两者? - JustLearningAgain

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接