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

Posted

技术标签:

【中文标题】NSFetchRequest 的 NSSortDescriptor 在上下文保存后不起作用【英文标题】:NSSortDescriptor of NSFetchRequest not working after context save 【发布时间】:2012-04-12 09:30:15 【问题描述】:

我正在 GCD 调度队列中对 NSManagedObjectContext 进行操作,定义如下:

- (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);
    ];

因为我一开始没有使用它而感到羞耻......

但是,我不知道为什么这会有所帮助,并希望得到解释。

【问题讨论】:

【参考方案1】:

我会尝试发表评论,因为我写了 MagicalRecord。

因此,在 iOS5 上,MagicalRecord 被设置为尝试使用多个托管对象上下文的新 Private Queue 方法。这意味着子上下文中的保存只会将保存推送到父上下文。只有当没有更多父母的父母保存时,保存才会持续到它的商店。这可能是您的 MagicalRecord 版本中发生的情况。

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

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

【讨论】:

感谢您的意见,我只尝试了更多线程隔离,但我更喜欢在 iOS 5 上使用私有队列。在测试期间,我认为我发现了一个错误。父上下文的保存在子上下文的线程上被调用,这会导致从 GCD 上下文递归保存时出现问题。我已经提交了一个拉取请求:github.com/magicalpanda/MagicalRecord/pull/159【参考方案2】:

当父上下文尚未保存到存储时,从带有排序描述符的子上下文中获取原始设置不起作用的实际根本原因是 Apple 错误:

NSSortdescriptor ineffective on fetch result from NSManagedContext

如果有任何方法可以避免嵌套上下文,请避免使用它们,因为它们仍然非常有问题,并且您可能会对假定的性能提升感到失望,参见。还: http://wbyoung.tumblr.com/post/27851725562/core-data-growing-pains

【讨论】:

【参考方案3】:

由于 CoreData 不是一个安全线程框架,并且对于每个线程(操作队列),核心数据使用不同的上下文。请参考以下优秀文章

http://www.duckrowing.com/2010/03/11/using-core-data-on-multiple-threads/

【讨论】:

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

以上是关于NSFetchRequest 的 NSSortDescriptor 在上下文保存后不起作用的主要内容,如果未能解决你的问题,请参考以下文章

如何将类型应用于NSFetchRequest实例?

嵌套 NSFetchRequest?

如何将类型应用于 NSFetchRequest 实例?

如何将类型应用于 NSFetchRequest 实例?

这个 NSFetchRequest 有啥问题?

NSFetchRequest / 谓词问题