我正在尝试实现此核心数据栈:
PSC <--+-- MainMOC
|
+-- BackgroundPrivateMOC
有些事情我实际上是不明白的。也许我们的Persisten Store中有一个对象,并且我们从主MOC提取了该对象以进行一些更改(用户手动对其进行更改)。同时,我的BG MOC对同一个对象进行了一些更改,并将更改保存到PS。保存完成后,我们必须将BG MOC合并到MAIN MOC(这是常见做法)。合并后,我期望MAIN MOC包含来自BG MOC的更改(因为所做的更改要比MAIN MOC稍晚一些)。但这实际上并没有发生。合并完成后,我所拥有的只是我的主MOC中的脏
refreshedObjects = 1
,如果我再次通过主MOC来获取该对象,则看不到通过BG MOC进行的任何更改。我应该如何在将BG更改正确传播到MAIN MOC的同时
在进行BG更改之前未保存MAIN MOC吗?
怎么处理
合并完成后我的主MOC具有非零
refreshedObjects
的情况,以及如何在主MOC中推送这些对象以使其可用于
拿来和一起?
我相信我的示例代码可以帮助您更清楚地了解我的问题。您可以下载项目(https://www.dropbox.com/s/1qr50zto5j4hj40/ThreadedCoreData.zip?dl=0)并运行我准备的XCTest。
这是失败的测试代码:
@implementation ThrdCoreData_Tests
- (void)setUp
{
[super setUp];
/**
OUR SIMPLE STACK:
PSC <--+-- MainMOC
|
+-- BackgroundPrivateMOC
*/
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
// main context (Main queue)
_mainMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[_mainMOC setPersistentStoreCoordinator:coordinator];
[_mainMOC setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
// background context (Private Queue)
_bgMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
_bgMOC.persistentStoreCoordinator = self.persistentStoreCoordinator;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(mergeBGChangesToMain:)
name:NSManagedObjectContextDidSaveNotification
object:_bgMOC];
u_int32_t value = arc4random_uniform(3000000000); // simply generate new random values for the test
_mainMOCVlaue = [NSString stringWithFormat:@"%u" , value];
_expectedBGValue = [NSString stringWithFormat:@"%u" , value/2];
Earthquake * mainEq = [Earthquake MR_findFirstInContext:self.mainMOC];
if (!mainEq){ // At the very first time the test is running, create one single test oject.
Earthquake * mainEq = [Earthquake MR_createEntityInContext:self.mainMOC];
mainEq.location = nil; // initial value will be nil
[self.mainMOC MR_saveOnlySelfAndWait];
}
}
- (void)testThatBGMOCSuccessfullyMergesWithMain
{
_expectation = [self expectationWithDescription:@"test finished"];
// lets change our single object in main MOC. I expect that the value will be later overwritten by `_expectedBGValue`
Earthquake * mainEq = [Earthquake MR_findFirstInContext:self.mainMOC];
NSLog(@"\nCurrently stored value:\n%@\nNew main value:\n%@", mainEq.location, _mainMOCVlaue);
mainEq.location = _mainMOCVlaue; // the test will succeed if this line commented
// now change that object in BG MOC by setting `_expectedBGValue`
[_bgMOC performBlockAndWait:^{
Earthquake * bgEq = [Earthquake MR_findFirstInContext:_bgMOC];
bgEq.location = _expectedBGValue;
NSLog(@"\nNew expected value set:\n%@", _expectedBGValue);
[_bgMOC MR_saveToPersistentStoreAndWait]; // this will trigger the `mergeBGChangesToMain` method
}];
[self waitForExpectationsWithTimeout:3 handler:nil];
}
- (void)mergeBGChangesToMain:(NSNotification *)notification {
dispatch_async(dispatch_get_main_queue(), ^{
[self.mainMOC mergeChangesFromContextDidSaveNotification:notification];
// now after merge done, lets find our object with expected value `_expectedBGValue`:
Earthquake * expectedEQ = [Earthquake MR_findFirstByAttribute:@"location" withValue:_expectedBGValue inContext:self.mainMOC];
if (!expectedEQ){
Earthquake * eqFirst = [Earthquake MR_findFirstInContext:self.mainMOC];
NSLog(@"\nCurrent main MOC value is:\n%@\nexptected:\n%@", eqFirst.location, _expectedBGValue);
}
XCTAssert(expectedEQ != nil, @"Expected value not found");
[_expectation fulfill];
});
}
最佳答案
首先,在发布核心数据代码时,建议您不要发布依赖于第三方库的代码,除非该第三方库与您的问题直接相关。我认为MR是神奇的记录,但我不使用它,它似乎只是在糊涂领域,因为谁知道它在幕后干什么(或不干什么)。
换句话说,请尝试将示例缩减到尽可能少的代码...而不再是更多...并且仅在绝对必要时才包括第三方库。
其次,在编写有关核心数据使用情况的单元测试时,建议使用内存中的堆栈。您总是开始为空,可以根据需要将其初始化。更容易用于测试。
就是说,您的问题是对mergeChangesFromContextDidSaveNotification
做什么(和不做什么)的误解。
基本上,您在Core Data持久性存储中有一个对象。您具有通过同一PSC连接到商店的两个不同的MOC。
然后,您的测试会将对象加载到主MOC中,并更改该值而不保存到PSC。然后,第二个MOC加载相同的对象,并将其值更改为其他值(即,商店,并且两个MOC都对同一对象的特定属性具有不同的值)。
现在,当我们保存MOC时,如果存在冲突,将按照mergePolicy
的指示处理冲突。但是,合并策略不适用于mergeChangesFromContextDidSaveNotification
。
您可以将mergeChangesFromContextDidSaveNotification
看作是插入任何新对象,删除所有删除的对象并“刷新”任何更新的对象,同时保留任何本地更改。
在测试中,如果添加另一个属性(例如“ title”)并在BG MOC中同时更改“ title”和“ location”,但仅在主MOC中更改“ location”,则将看到“ title”得到按预期从BG MOC合并到主要MOC。
但是,正如您在问题中指出的那样,“位置”似乎没有合并。实际上,它确实会合并,但是任何本地更改都将覆盖商店中的内容……而这正是您想要发生的事情,因为用户可能进行了更改,并且不希望在后台进行更改。
基本上,任何待处理的本地更改都将覆盖要合并的MOC中的更改。
如果您想要不同的东西,则必须在执行合并时实现该行为,如下所示...
- (void)mergeBGChangesToMain:(NSNotification*)note {
NSMutableSet *updatedObjectIDs = [NSMutableSet set];
for (NSManagedObject *obj in [note.userInfo objectForKey:NSUpdatedObjectsKey]) {
[updatedObjectIDs addObject:[obj objectID]];
}
[_mainMOC performBlock:^{
for (NSManagedObject *obj in [_mainMOC updatedObjects]) {
if ([updatedObjectIDs containsObject:obj.objectID]) {
[_mainMOC refreshObject:obj mergeChanges:NO];
}
}
[_mainMOC mergeChangesFromContextDidSaveNotification:note];
}];
}
该代码首先收集在MOC合并中更新的每个对象的
ObjectID
。在进行合并之前,我们然后查看合并到MOC中的每个更新的对象。如果我们将一个对象合并到我们的MOC中,并且我们的“合并到MOC”也更改了该对象,则我们希望允许“从MOC合并”中的值覆盖“合并到MOC”中的值。因此,我们从存储中刷新本地对象,基本上丢弃了任何本地更改(存在副作用,例如,导致该对象成为故障,释放对任何关系的引用以及释放任何临时属性-请参见refreshObject:mergeChanges文档:)。
考虑以下类别,该类别可解决您的情况,并使用
NSFetchedResultsController
之类的观察器时会出现一个常见问题。@interface NSManagedObjectContext (WJHMerging)
- (void)mergeChangesIntoContext:(NSManagedObjectContext*)moc
withDidSaveNotification:(NSNotification*)notification
faultUpdatedObjects:(BOOL)faultUpdatedObjects
overrideLocalChanges:(BOOL)overrideLocalChanges
completion:(void(^)())completionBlock;
@end
@implementation NSManagedObjectContext (WJHMerging)
- (void)mergeChangesIntoContext:(NSManagedObjectContext *)moc
withDidSaveNotification:(NSNotification *)notification
faultUpdatedObjects:(BOOL)faultUpdatedObjects
overrideLocalChanges:(BOOL)overrideLocalChanges
completion:(void (^)())completionBlock {
NSAssert(self == notification.object, @"Not called with");
NSSet *updatedObjects = notification.userInfo[NSUpdatedObjectsKey];
NSMutableSet *updatedObjectIDs = nil;
if (overrideLocalChanges || faultUpdatedObjects) {
updatedObjectIDs = [NSMutableSet setWithCapacity:updatedObjects.count];
for (NSManagedObject *obj in updatedObjects) {
[updatedObjectIDs addObject:[obj objectID]];
}
}
[moc performBlock:^{
if (overrideLocalChanges) {
for (NSManagedObject *obj in [moc updatedObjects]) {
if ([updatedObjectIDs containsObject:obj.objectID]) {
[moc refreshObject:obj mergeChanges:NO];
}
}
}
if (faultUpdatedObjects) {
for (NSManagedObjectID *objectID in updatedObjectIDs) {
[[moc objectWithID:objectID] willAccessValueForKey:nil];
}
}
[moc mergeChangesFromContextDidSaveNotification:notification];
if (completionBlock) {
completionBlock();
}
}];
}
@end
关于ios - 多上下文核心数据:合并到未保存的上下文,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/28520133/