Master Data and GCD: Passing the Correct Managed Object Context to Custom NSManagedObjects

I am getting runtime errors that appear to be due to my incorrect implementation GCD

combined with my custom NSManagedObjects.

Nested in the call GCD

, I'm using custom NSManagedObjects, which (appears to) have their own managed object contexts (= self.managedObjectContext

).

I am creating a context of managed objects in the delegate application using the context of a managed object, provided UIManagedDocument

: self.managedDocument.managedObjectContext

.

I don't understand how to pass the correct managed object context to my custom NSManagedObjects. How can I change the code to use the correct managed object context?

This is my main method (inside the view controller):

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

      

In this main method, I have no reference to the context of the managed object, I just call valueForCoa:convertedTo:

(which is coded like this):

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

      

valueForCoa

is a method in my custom NSManagedObject subclass ReportedPeriod

and uses its (default) managed object context self.managedObjectContext

.

Then the application usually crashes in the custom NSManagedObject subclass CoaMap

as follows when it makes a fetch request:

+ (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];
// ...
}

      

Error message: Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSCFSet: 0x9a8a4a0> was mutated while being enumerated.

Could you please help me with this problem and give me some advice on how to improve my code to pass the correct contexts of managed objects (or how to make sure that the correct context is used in all methods)?

Many thanks!

+3


source to share


2 answers


This error is usually associated with an incorrect managed entity context on different threads or queues. You created the MOC in the main queue, but you are using it in the background without considering this fact. It is wrong to use MOC in the background, but you need to be aware of this and start preparing.

You didn't say how you create the MOC. I suggest you do this:

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

      

With the main concurrency queue, you can just use it normally on the main thread. If you are in the submission queue, do the following:



[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];
    // ...
}];

      

This will ensure that the MOC work is done on the main thread, even if you are in the background. (Technically, this means that background MOC work will be properly synchronized with work running in the main queue, but the result is the same: it's a safe way to do it.)

A similar approach would be to use NSPrivateQueueConcurrencyType

. If you do, you should use performBlock

or performBlockAndWait

everywhere for MOC, not just background threads.

+7


source


Firstly,

"how to pass the correct managed object context to my custom NSManagedObjects.

Create NSManagedObject

with NSManagedObjectContext

. And not vice versa. So when you have NSManagedObject

, you can access by NSManagedObjectContext

requesting its property: – managedObjectContext

as stated in the Apple Document

Secondly,

When working with CoreData, multithreading can be a little tricky. especially for a beginner. All the details you need to take care of.

I highly recommend you check Parent-Child NSManagedContext

. then using MagicRecord .



Using MagicRecord , you can simply Grand Central Dispatch with a block like this:

[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.

}];

      

If you need to pass NSManagedObject

to this block, remember to only pass NSManagedObjectID

proprety instead.

This is an example.

NSManagedObjectID *coaMapID = CoaMap.objectID;

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

      

Hope it helped.

0


source







All Articles