NSFetchRequest的NSSortDescriptor在上下文保存后不起作用

5

我正在使用类似以下方式定义的NSManagedObjectContext在GCD调度队列中进行操作:

- (NSManagedObjectContext *)backgroundContext
{
    if (backgroundContext == nil) {
        self.backgroundContext = [NSManagedObjectContext MR_contextThatNotifiesDefaultContextOnMainThread];
    }
    return backgroundContext;
}

MR_contextThatNotifiesDefaultContextOnMainThread 是来自 MagicalRecord 的一个方法:

NSManagedObjectContext *context = [[self alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[context setParentContext:[NSManagedObjectContext MR_defaultContext]];
return context;

在获取了我的对象并将它们放置在正确的队列位置后,我记录了它们,顺序是正确的。但是,第二个日志似乎完全是随机的,排序描述符显然没有起作用。
我已经缩小问题范围到[self.backgroundContext save:&error]。保存后台上下文后,排序描述符就无法正常工作了。
dispatch_group_async(backgroundGroup, backgroundQueue, ^{
    // ...

    for (FooObject *obj in fetchedObjects) {
        // ...
        obj.queuePosition = [NSNumber numberWithInteger:newQueuePosition++];
    }

    NSFetchRequest *f = [NSFetchRequest fetchRequestWithEntityName:[FooObject entityName]];
    f.predicate = [NSPredicate predicateWithFormat:@"queuePosition > 0"];
    f.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"queuePosition" ascending:YES]];
    NSArray *queuedObjects = [self.backgroundContext executeFetchRequest:f error:nil];
    for (FooObject *obj in queuedObjects) {
        DLog(@"%@ %@", obj.queuePosition, obj.title);
    }

    if ([self.backgroundContext hasChanges]) {
        DLog(@"Changes");
        NSError *error = nil;
        if ([self.backgroundContext save:&error] == NO) {
            DLog(@"Error: %@", error);
        }
    }

    queuedObjects = [self.backgroundContext executeFetchRequest:f error:nil];
    for (FooObject *obj in queuedObjects) {
        DLog(@"%@ %@", obj.queuePosition, obj.title);
    }

});

我不知道为什么排序描述符不起作用,有没有Core Data专家能帮忙?

更新:

iOS 4上没有出现这个问题。我想原因可能在于线程隔离和私有队列模式之间的差异。MagicalRecord自动使用新的并发模式,其行为似乎不同。

更新2:

通过添加后台上下文的保存,问题已得到解决:

if ([[NSManagedObjectContext MR_contextForCurrentThread] hasChanges]) {
    DLog(@"Changes");
    NSError *error = nil;
    if ([[NSManagedObjectContext MR_contextForCurrentThread] save:&error] == NO) {
        DLog(@"Error: %@", error);
    } else {
        NSManagedObjectContext *parent = [NSManagedObjectContext MR_contextForCurrentThread].parentContext;
        [parent performBlockAndWait:^{
            NSError *error = nil;
            if ([parent save:&error] == NO) {
                DLog(@"Error saving parent context: %@", error);
            }
        }];
    }
}

更新3:

MagicalRecord提供了一种递归保存上下文的方法,现在我的代码看起来像这样:

if ([[NSManagedObjectContext MR_contextForCurrentThread] hasChanges]) {
    DLog(@"Changes");
    [[NSManagedObjectContext MR_contextForCurrentThread] MR_saveWithErrorHandler:^(NSError *error) {
        DLog(@"Error saving context: %@", error);
    }];
}

我真该早点使用它...

但是,我不知道为什么它会有帮助,我很想得到一个解释。

3个回答

2

我会尝试进行评论,因为我写了MagicalRecord。

所以,在iOS5上,MagicalRecord被设置为尝试使用多个托管对象上下文的新私有队列方法。这意味着在子上下文中保存只会将保存推送到父级。只有当没有更多父级的父级保存时,保存才会保留到其存储区。这可能是您版本的MagicalRecord中发生的情况。

MagicalRecord已经在后续版本中尝试为您处理此问题。也就是说,它会尝试在私有队列模式和线程隔离模式之间进行选择。正如您发现的那样,这并不太有效。在iOS4和iOS5上编写代码的唯一真正兼容的方式(无需复杂的预处理器规则等)是使用经典的线程隔离模式。从1.8.3标记开始,MagicalRecord支持该模式,并且应该适用于两者。从2.0开始,这里将仅使用私有队列。

如果您查看MR_save方法,您还会发现它也为您执行hasChanges检查(这也可能是不必要的,因为Core Data内部也可以处理)。总之,您应该写和维护的代码更少...


1
感谢您的输入,我对线程隔离进行了更多的实验,但我更倾向于在iOS 5上使用私有队列。在测试过程中,我发现了一个错误。父上下文的保存被调用在子上下文的线程上,这导致了在从GCD上下文递归保存时出现问题。我已经提交了一个Pull Request:https://github.com/magicalpanda/MagicalRecord/pull/159 - tim

1

你原来的设置无法正常工作的实际根本原因是苹果存在一个 bug,当父级上下文尚未保存到存储器时,从子上下文中使用排序描述符进行获取会出现问题:

NSSortdescriptor ineffective on fetch result from NSManagedContext

如果有任何方法可以避免嵌套上下文,请尽量避免使用它们,因为它们仍然存在极大的错误,并且您可能会对所谓的性能提升感到失望,参见: http://wbyoung.tumblr.com/post/27851725562/core-data-growing-pains


-2

backgroundContext是从GCD块初始化的,没有ManagedObject或ManagedObjectContext跨越线程边界,所以我认为这不是我的问题。 - tim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接