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

时间:2014-10-30 20:30:54

标签: xcode core-data sync magicalrecord

我正在努力在客户端和服务器之间同步数据。我正在使用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:]之前调用的内容。

有什么想法吗?我已经在这几天面对这件事。

2 个答案:

答案 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