3

我收到运行时错误,这似乎是由于我不正确地GCD结合我的自定义 NSManagedObjects 实现的。

嵌套在GCD调用中,我正在使用自定义 NSManagedObjects,它(似乎)有自己的托管对象上下文(= self.managedObjectContext)。

我正在使用提供的托管对象上下文在应用程序委托中创建托管对象上下文UIManagedDocumentself.managedDocument.managedObjectContext

我不明白如何将正确的托管对象上下文传递给我的自定义 NSManagedObjects。我需要如何更改我的代码以使用正确的托管对象上下文?

这是我的主要方法(在​​视图控制器内):

dispatch_queue_t queue;
queue = dispatch_queue_create("queue", NULL);
dispatch_async(queue, ^{
// ...
NSDecimalNumber *value = [reportedPeriod 
   valueForCoa:figure.code 
   convertedTo:self.currencySymbol];
// ...});
}

在这个主要方法中,我没有对托管对象上下文的任何引用,我只是调用valueForCoa:convertedTo:(编码如下):

- (NSDecimalNumber*)valueForCoa:(NSString*)coaStr
convertedTo:(NSString*)targetCurrencyStr {
// ...
CoaMap *coa = [CoaMap coaItemForString:coaStr
   inManagedObjectContext:self.managedObjectContext];
// ...
}

valueForCoa是我的自定义子类 NSManagedObject 中的一种方法,ReportedPeriod并使用其(默认)托管对象上下文self.managedObjectContext

CoaMap然后,应用程序在执行 fetch 请求时通常会在以下方法中的自定义子类 NSManagedObject 中崩溃:

+ (CoaMap*)coaItemForString:(NSString*)coaStr 
inManagedObjectContext:(NSManagedObjectContext*)context {

NSFetchRequest *request = [NSFetchRequest 
fetchRequestWithEntityName:NSStringFromClass([self class])];
NSPredicate *predicate = 
   [NSPredicate predicateWithFormat:@"coa == %@",coaStr];
   request.predicate = predicate;
// ** The runtime error occurs in the following line **
NSArray *results = [context executeFetchRequest:request error:nil];
// ...
}

错误信息是:Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x9a8a4a0> was mutated while being enumerated.

您能否帮我解决这个问题,并就如何改进我的代码以传递正确的托管对象上下文(或如何确保在所有方法中使用正确的上下文)给我一些建议?

非常感谢!

4

2 回答 2

7

该错误通常与跨不同线程或队列错误地使用托管对象上下文有关。您在主队列上创建了 MOC,但您在后台队列上使用它而不考虑这一事实。在后台队列上使用 MOC 并没有,但您需要注意这一点并做好准备。

你没有说你是如何创建 MOC 的。我建议你应该这样做:

NSManagedObjectContext *context = [[NSManagedObjectContext alloc]
    initWithConcurrencyType: NSMainQueueConcurrencyType];

使用主队列并发,您可以在主线程上正常使用它。但是,当您在调度队列中时,请执行以下操作:

[context performBlockAndWait:^{
    NSFetchRequest *request = [NSFetchRequest 
        fetchRequestWithEntityName:NSStringFromClass([self class])];
    NSPredicate *predicate = 
       [NSPredicate predicateWithFormat:@"coa == %@",coaStr];
    request.predicate = predicate;
    NSArray *results = [context executeFetchRequest:request error:nil];
    // ...
}];

这将确保 MOC 的工作发生在主线程上,即使您在后台队列中也是如此。(从技术上讲,这实际上意味着 MOC 在后台的工作将与它在主队列上的工作正确同步,但结果是相同的:这是执行此操作的安全方法)。

类似的方法是使用NSPrivateQueueConcurrencyType。如果你这样做,你会在 MOC 的任何地方使用performBlock或使用performBlockAndWait,而不仅仅是在后台线程上。

于 2013-02-01T17:12:11.550 回答
0

第一的,

“如何将正确的托管对象上下文传递给我的自定义 NSManagedObjects。”

我们NSManagedObjectNSManagedObjectContext. 不是相反。因此,当您拥有 时NSManagedObject,您可以NSManagedObjectContext通过询问其属性来访问 :如Apple Document– managedObjectContext中所列

第二,

使用 CoreData 时,多线程可能有点棘手。特别是对于初学者。这些都是您需要注意的细节。

我强烈建议您查看Parent-Child NSManagedContext. 然后,使用MagicRecord

通过使用MagicRecord,您可以简单地使用带有这样的块的 Grand Central Dispatch:

[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext){

    // here use the `localContext` as your NSManagedContext and do things in the background.
    // it will take care of all the rest.

}];

如果你需要传递NSManagedObject到这个块,记住只传递NSManagedObjectID而不是属性。

这是一个例子。

NSManagedObjectID *coaMapID = CoaMap.objectID;

[MagicalRecord saveInBackgroundWithBlock:^(NSManagedObjectContext *localContext){
    coaMap *aCoaMap = (coaMap *)[localContext existingObjectWithID:coaMapID error:&error];
    // then use aCoaMap normally.
}];

希望这有帮助。

于 2013-02-01T17:21:00.720 回答