我正在努力在客户端和服务器之间同步数据。我正在使用MagicalRecord
(核心数据包装器)在客户端上存储数据。我有一个名为Dirty
的实体,它包含一个名为dirty
的属性。这表示客户端上是否有尚未推送到服务器的更改。只要在类上设置了属性,dirty
就会设置为[NSDate date]
(当然,设置dirty
时,会设置正确的值)。在客户端上创建的每个其他实体都继承自Dirty
。我们的想法是,在推送所有客户端数据之前,我们不会从服务器获取新数据(如果所有实体都有dirty == nil
,则只获取新数据。)
从服务器导入数据时(使用+[NSManagedObject MR_importFromObject:inContext:
),每个实体的dirty
属性都设置为nil
(因为客户端与服务器是最新的)。
在启动保存之前(+[MagicalRecord saveWithBlock:completion:]
保存区内),dirty
仍为nil
。但是,在完成块中,获取刚刚保存的实体(在主线程上)具有dirty
的值。
在保存期间,实体将转移到主线程的上下文中。但是,有一个问题,因为-[NSManagedObject didChangeValueForKey:]
被调用从localContext
(后台线程)传递到主上下文(在主线程上)的每个属性。 dirty
为每个实体设置[NSDate date]
。大多数情况下,dirty
最后没有设置,这意味着当设置了另一个属性时,dirty
会被覆盖。
有没有办法确保dirty
是将NSManagedObject实例传输到主线程的上下文时设置的最后一个属性?我甚至愿意在保存对象时设置dirty
(而不是在设置属性时)。
我尝试了各种选项,包括核对-[NSManagedObject isInserted]
内的-[NSManagedObject isUpdated]
和-[NSManagedObject didChangeValueForKey:]
。另一件令人讨厌的事情是在传输属性之前插入新对象(我以为我可以使用某种标志来锁定/解锁设置dirty
)。
需要注意的另一件事是[NSManagedObject(_NSInternalMethods) _updateFromRefreshSnapshot:includingTransients:]
是在新对象上调用-[NSManagedObject didChangeValueForKey:]
之前调用的内容。
有什么想法吗?我已经在这几天面对这件事。
答案 0 :(得分:1)
在SO 10723861
中查看Paul de Lange的答案那里的'TrackedEntity'将是您的Dirty
实体,属性lastModified
会转换为您的属性'dirty'
在保存期间(通过观察NSManagedObjectContextWillSaveNotification
触发),-objectContextWillSave
方法会将插入的对象和更新的对象合并到一个集合中。然后它遍历对象集并使用时间戳更新lastModified
属性。
---更新(wrt。clientUpdatedAt
)
您也可以查看at this one。它解释了如何使用一些额外的字段来协助同步。使用额外属性sync_status
应有助于确定是否需要上传实体。希望有所帮助
答案 1 :(得分:0)
在浪费了大量时间研究潜在的解决方案后,我回过头来看看以最简单的方式解决这个问题。以下是我提出的建议:
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
。