gpt4 book ai didi

ios - 多上下文核心数据 : merging to unsaved context

转载 作者:行者123 更新时间:2023-11-28 18:58:55 26 4
gpt4 key购买 nike

我正在尝试实现这个核心数据栈:

PSC <--+-- MainMOC
|
+-- BackgroundPrivateMOC

有些事情我其实不明白。也许我们的 Persisten Store 中有一个对象,我们从主 MOC 中获取它以进行一些更改(用户手动更改它)。同时,我的 BG MOC 正在对同一个对象进行一些更改,并将更改保存到 PS。保存完成后,我们必须将 BG MOC 合并到 MAIN MOC(这是一种常见做法)。合并后我期望的是 MAIN MOC 包含来自 BG MOC 的更改(因为这些更改比 MAIN 晚了一点)。但这实际上并没有发生。合并完成后,我的 MAIN MOC 中只有一个脏的 refreshedObjects = 1,如果我通过 MAIN MOC 再次获取该对象,我看不到通过 BG MOC 进行的任何更改。

  • 我应该如何正确地将 BG 更改传播到 MAIN MOC,同时在更改 BG 之前未保存 MAIN MOC?
  • 如何处理合并完成后我的 MAIN MOC 具有非零 refreshedObjects 的情况,以及如何将这些对象推送到 MAIN 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 持久存储中有一个对象。您有两个不同的 MOC 通过同一个 PSC 连接到商店。

然后您的测试将对象加载到主 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];
}];
}

该代码首先收集在 merged-from-MOC 中更新的每个对象的 ObjectID

在进行合并之前,我们会查看合并到 MOC 中的每个更新对象。如果我们将一个对象合并到我们的 MOC 中,并且我们的 merge-to-MOC 也更改了该对象,那么我们希望允许 merged-from-MOC 中的值覆盖 merged-to-MOC 中的值。因此,我们从存储中刷新本地对象,基本上丢弃任何本地更改(有副作用,例如,导致对象成为错误,释放对任何关系的引用,并释放任何 transient 属性 - 请参阅 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 - 多上下文核心数据 : merging to unsaved context,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28520133/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com