3
votes

I'm not exactly sure what's going on with this, but I could use some help. I'm attempting to perform a query in the background every time a user enters text into a UITextField. I've been reading around and it looks like this is how I should be performing background CoreData operations, but I keep getting this error:

"Can only use -performBlock: on an NSManagedObjectContext that was created with a queue"

I googled this error, but every solution was saying that my context needs to be created using the PrivateQueueConcurrentcyType, which I did. Not sure why this is happening. Maybe it's a bug in the new iOS?

let managedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = appDelegate.persistentStoreCoordinator
if !textField.text.isEmpty {
    manatedObjectContext.performBlock {
         let objectIDs = //query to get the object IDs

         appDelegate.managedObjectContext.performBlock {
             var objects = Object[]()

             for id in objectsIDs {
                 objects += appDelegate.managedObjectContext.objectWithID(id) as Object
             }

             self.searchResults = objects
             self.searchResultsTableView.reloadData()
         }
    }
}

EDIT: I fixed my issue by doing the following:

  • Setting the parent context for private managedObjectContext as my main context from the AppDelegate
  • I removed the line that sets the persistent store because it's no longer needed when setting the parent context as the main context
  • I also changed default implementation for creating the main context to explicitly create it using MainQueueConcurrencyType
2
Nice Swift (-: You are likely having an issue with managedObjectContext.psc = appDelegate.psc. You probably should make your threadMOC depend on your mainMOC using -setParentContext: instead. See my complete example below.SwiftArchitect
@gothicdev Thanks! Settings the parent context and changing the main context to be explicitly created using "MainQueueConcurrencyType" fixed that issue.Homeschooldev

2 Answers

2
votes

Example taken from an actual published app, you should make use of 2+ MOCs.

  1. One for the main thread
  2. One for each background thread.

This allows for background threads to safely perform MOC operations, without running the risk of conflicting with each other. Notice how the secondary MOC(s) use the -setParentContext: to refer to the main MOC.

This backgroundMOC, when created and used from a background thread, is safe and won't throw the error.

When you are done with your modifications, perform a save on the background thread:

[backgroundMOC performBlockAndWait:^{
    if([backgroundMOC hasChanges]) {
        NSError * error;
        [backgroundMOC save:&error];
        // handle error
    }
}];

To create both MOCs used in the example above, you could use the code below (ObjC)

For main thread, use:

@property(strong) NSManagedObjectContext * 

- (NSManagedObjectContext *)mainMOC mainManagedObjectContext;
{
    NSThread * currentThread = [NSThread currentThread];
    NSAssert([currentThread isMainThread], @"managedObjectContext invoked from %@",currentThread);
    if([currentThread isMainThread]) {
        if(!_mainManagedObjectContext) {
            NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];

            if(coordinator) {
                _mainManagedObjectContext = [[NSManagedObjectContext alloc]
                                             initWithConcurrencyType:NSMainQueueConcurrencyType];
                [_mainManagedObjectContext setPersistentStoreCoordinator: coordinator];
                [_mainManagedObjectContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
            }
        }

        return _mainManagedObjectContext;
    }
    return nil;
}

For background thread, use:

- (NSManagedObjectContext *)threadMOCWithMainMOC:(NSManagedObjectContext *)mainMOC
{
    NSManagedObjectContext * threadManagedObjectContext = [[NSManagedObjectContext alloc]
                                       initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    [threadManagedObjectContext setParentContext:[self.db mainManagedObjectContext]];
    [threadManagedObjectContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
    return threadManagedObjectContext;
}
0
votes

The problem is not with the MOC you constructed in the code you provided, but with the MOC you are getting from the app delegate. I'd bet that is being constructed with the apple-provided core data template... which (very unfortunately) still creates MOCs with the default confinement type...

This is what it is complaining about...

appDelegate.managedObjectContext.performBlock {