我得到的运行时错误似乎是由于我的GCD
与我的自定义NSManagedObjects的错误实现造成的。
嵌套在GCD
调用中,我使用自定义NSManagedObjects(似乎)有自己的托管对象上下文(= self.managedObjectContext
)。
我使用UIManagedDocument
:self.managedDocument.managedObjectContext
提供的托管对象上下文在app委托中创建托管对象上下文。
我不明白如何将正确的托管对象上下文传递给我的自定义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
。
在执行获取请求时,应用程序通常会在以下方法中在自定义子类NSManagedObject CoaMap
中崩溃:
+ (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.
您能否帮我解决这个问题,并就如何改进我的代码以传递正确的托管对象上下文(或如何确保在所有方法中使用正确的上下文)提供一些建议?
非常感谢!
答案 0 :(得分: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
代替。如果你这样做,你可以在任何地方使用performBlock
或performBlockAndWait
作为MOC,而不仅仅是在后台线程上。
答案 1 :(得分:0)
首先,
“如何将正确的托管对象上下文传递给我的自定义NSManagedObjects。”
我们使用NSManagedObject
创建NSManagedObjectContext
。而不是相反。因此,如果您有NSManagedObject
,则可以通过询问Apple Document
NSManagedObjectContext
来访问– managedObjectContext
其次,
使用CoreData时,多线程可能有点棘手。特别是初学者。这些都是您需要照顾的细节。
我强烈建议您查看Parent-Child NSManagedContext
。然后,使用MagicRecord。
使用MagicRecord,你可以简单地使用像这样的Block进行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.
}];
希望这会有所帮助。