使用带有 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
不是一个足够好的指示,因为当服务器用服务器的updatedAt
time 更新客户端上的实体时,实体必须保存并且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 很好地同步?