使用带有 Core Data 的脏标志与服务器同步

Posted

技术标签:

【中文标题】使用带有 Core Data 的脏标志与服务器同步【英文标题】:Using a dirty flag with Core Data for synchronizing with a server 【发布时间】:2014-10-30 20:30:53 【问题描述】:

我正在处理客户端和服务器之间的数据同步。我正在使用MagicalRecord(核心数据包装器)将数据存储在客户端上。我有一个名为Dirty 的实体,它包含一个名为dirty 的属性。这表示客户端上是否有尚未推送到服务器的更改。每当在类上设置属性时,dirty 都会设置为 [NSDate date](当然,在设置 dirty 时,会设置正确的值)。在客户端上创建的所有其他实体都继承自 Dirty。我们的想法是,在所有客户端数据都被推送之前,我们不会从服务器获取新数据(只有在所有实体都有 dirty == nil 时才获取新数据)。

从服务器导入数据时(使用+[NSManagedObject MR_importFromObject:inContext:),每个实体的dirty 属性设置为nil(因为客户端与服务器保持同步)。

就在保存开始之前(在+[MagicalRecord saveWithBlock:completion:] 保存块内),dirty 仍然是nil。但是,在完成块中,获取刚刚保存的实体(在主线程上)具有dirty 的值。

在保存期间,实体被转移到主线程的上下文中。但是,存在一个问题,因为从localContext(后台线程)传输到主上下文(在主线程上)的每个属性都会调用-[NSManagedObject didChangeValueForKey:]dirty 为每个实体设置[NSDate date]。大多数情况下,dirty 不会最后设置,这意味着当设置另一个属性时,dirty 会被覆盖。

有没有办法确保dirty 是在将 NSManagedObject 实例传输到主线程上下文时设置的最后一个属性?我什至愿意在保存对象时设置dirty(而不是在设置属性时)。

我尝试了各种选项,包括检查 -[NSManagedObject didChangeValueForKey:] 中的 -[NSManagedObject isInserted]-[NSManagedObject isUpdated]。另一件有点烦人的事情是新对象是在属性传输之前插入的(我想我可以有某种标志来锁定/解锁设置dirty)。

另外需要注意的是[NSManagedObject(_NSInternalMethods) _updateFromRefreshSnapshot:includingTransients:] 是在-[NSManagedObject didChangeValueForKey:] 被新对象调用之前被调用的。

有什么想法吗?这几天我一直在打脸。

【问题讨论】:

【参考方案1】:

看看 Paul de Lange 在 SO 10723861 中的回答

那里的 'TrackedEntity' 将是您的 Dirty 实体,而属性 lastModified 将转换为您的属性 'dirty'

在保存期间(通过观察NSManagedObjectContextWillSaveNotification 触发)-objectContextWillSave 方法会将插入的对象和更新的对象合并到一个集合中。然后它遍历对象集并使用时间戳更新lastModified 属性。

--- 更新(写clientUpdatedAt

你可能也想看看at this one。它解释了如何使用一些额外的字段来帮助同步。使用额外的属性sync_status 应该有助于确定是否需要上传实体。希望有帮助

【讨论】:

所以我已经在使用clientUpdatedAt(和clientCreatedAt)进行此操作。 clientUpdatedAt 不是一个足够好的指示,因为当服务器用服务器的updatedAttime 更新客户端上的实体时,实体必须保存并且clientUpdatedAt 再次用+[NSDate date] 更新。如果我检查clientUpdatedAt 是否比updatedAt 更新,它每次都会返回YES。 :// 我已经更新了我的答案,你可能需要引入更多的属性来处理同步 感谢您更新您的答案。挑战在于这一步:set sync_status to 1 on your model object whenever something changes and needs to be synchronized to the server。这与我在代码中遇到的挑战相同。我尝试使用didChangeValueForKey:,但由于我在原始帖子中提到的原因,这并不可靠。您对如何完成此步骤有任何建议吗?一旦弄清楚这一点,其他一切都会一帆风顺。 我正在考虑在保存块中保留脏值,并在完成块中确保值相同。如果没有,那么我将再次设置它们并将它们保存在主上下文中。不过,这变得非常讨厌。【参考方案2】:

在潜在的解决方案上浪费了大量时间后,我回过头来想用最简单的方式解决这个问题。这是我想出的:

1) 删除-[Dirty didChangeValueForKey:]

2) 创建了一个BTCoreDataService 类,并添加了以下方法:

+ (void)saveClientChangesWithSaveBlock:(BTLocalManagedObjectContextBlock)saveBlock
                       completionBlock:(MRSaveCompletionHandler)completionBlock 
    [self saveAndSetDirty:[NSDate date] 
                saveBlock:saveBlock 
          completionBlock:completionBlock];


+ (void)saveServerChangesWithSaveBlock:(BTLocalManagedObjectContextBlock)saveBlock
                       completionBlock:(MRSaveCompletionHandler)completionBlock 
    [self saveAndSetDirty:nil 
                saveBlock:saveBlock 
          completionBlock:completionBlock];


#pragma mark - Internal

+ (void)saveAndSetDirty:(NSDate *)dirty
              saveBlock:(BTLocalManagedObjectContextBlock)saveBlock
        completionBlock:(MRSaveCompletionHandler)completionBlock 
    [MagicalRecord
     saveWithBlock:^(NSManagedObjectContext *localContext) 
         if (saveBlock) 
             saveBlock(localContext);

             [[localContext BT_insertedAndUpdatedAtObjectsKindOfClass:[Dirty class]]
              makeObjectsPerformSelector:@selector(setDirty:) withObject:dirty];
         
     
     completion:completionBlock];

这是NSManagedObjectContext+BTManagedObjectContext的实现:

- (NSSet *)BT_insertedObjectsKindOfClass:(Class)cls 
    return
    [self.insertedObjects
     filteredSetUsingPredicate:[self BT_isKindOfClassPrediate:cls]];


- (NSSet *)BT_updatedObjectsKindOfClass:(Class)cls 
    return
    [self.updatedObjects
     filteredSetUsingPredicate:[self BT_isKindOfClassPrediate:cls]];


- (NSSet *)BT_insertedAndUpdatedAtObjectsKindOfClass:(Class)cls 
    return
    [[self BT_insertedObjectsKindOfClass:cls]
     setByAddingObjectsFromSet:[self BT_updatedObjectsKindOfClass:cls]];


#pragma mark - Internal

- (NSPredicate *)BT_isKindOfClassPrediate:(Class)cls 
    return [NSPredicate predicateWithFormat:@"self isKindOfClass:%@", cls];

现在唯一要做的就是记住使用BTCoreDataService来保存对象,而不是直接使用MagicalRecord

【讨论】:

以上是关于使用带有 Core Data 的脏标志与服务器同步的主要内容,如果未能解决你的问题,请参考以下文章

在 OS X 服务器上运行的 DB 可以与 iOS 上的 Core Data 很好地同步?

将 Core Data XML 存储同步到磁盘

如何将 sqlite 数据库与 Core Data 同步?

Core Data 与 Ensembles 的 iCloud 同步

如何将 Core Data 与引用的文件同步?

将数据与 Core Data 同步