I've sublcassed an NSOperation and set my completionBlock but it seems to never enter even when the operation finishes. Here's my code:
A catalog controller class sets up the NSOperation:
- (void)setupOperation {
...
ImportWordOperation *importWordOperation = [[ImportWordOperation alloc] initWithCatalog:words];
[importWordOperation setMainObjectContext:[app managedObjectContext]];
[importWordOperation setCompletionBlock:^{
[(ViewController *)[[app window] rootViewController] fetchResults];
}];
[[NSOperationQueue mainQueue] addOperation:importWordOperation];
[importWordOperation release];
...
}
As you can see, I'm setting the completion block to execute a method on the main thread, in some other controller.
Then, in main
my subclassed NSOperation class: ImportWordOperation.m
, I fire-up the background operation. I even overrode isFinished
iVar in order for the completion method to be triggered:
- (void)setFinished:(BOOL)_finished {
finished = _finished;
}
- (BOOL)isFinished {
return (self.isCancelled ? YES: finished);
}
- (void)addWords:(NSDictionary *)userInfo {
NSError *error = nil;
AppDelegate *app = [AppDelegate sharedInstance];
NSManagedObjectContext *localMOC = [userInfo valueForKey:@"localMOC"];
NSEntityDescription *ent = [NSEntityDescription entityForName:@"Word" inManagedObjectContext:localMOC];
for (NSDictionary *dictWord in [userInfo objectForKey:@"words"]) {
Word *wordN = [[Word alloc] initWithEntity:ent insertIntoManagedObjectContext:localMOC];
[wordN setValuesForKeysWithDictionary:dictWord];
[wordN release];
}
if (![[userInfo valueForKey:@"localMOC"] save:&error]) {
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
[localMOC reset];
[self setFinished:YES];
}
- (void)main {
finished = NO;
NSManagedObjectContext *localMOC = nil;
NSUInteger type = NSConfinementConcurrencyType;
localMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:type];
[localMOC setUndoManager:nil];
[localMOC setParentContext:[self mainObjectContext]];
if (![self isCancelled]) {
if ([self.words count] > 0) {
[self performSelectorInBackground:@selector(addWords:) withObject:@{@"words":self.words, @"localMOC":localMOC}];
}
}
}
If I remove isFinished accessor methods, then the completion block gets called but way before ImportWordOperation
finishes.
I've read code that I've found that uses its own completion block but then what's the use for the completion block in NSOperation subclasses anyway?
Any ideas or point to a similar solved situation would be greatly appreciated.
I encountered this error while implementing an asynchronous subclass of
NSOperation
.The Swift way of referencing key paths is with the
#keyPath
directive, so I was doing this (_executing
and_finished
are my internal variables):Unfortunately, the
#keyPath
expressions above resolve to"executing"
and"finished"
, respectively, and we need to throw KVO notifications for"isExecuting"
and"isFinished"
. This is why thecompletionBlock
is not getting invoked.The solution is to hard code them as such:
You've kind of fallen into a weird space between concurrent and non-concurrent
NSOperation
subclasses here. Typically, when you implementmain
, your operation is non-concurrent, andisFinished
changes toYES
as soon asmain
exits.However, you've provided your own implementation of
isFinished
, and also coded it so thatisFinished
doesn't returnYES
until aftermain
has exited. This makes your operation start to act like a concurrent operation in many ways - at least including the need to manually emit KVO notifications.The quick solution to your problem is to implement
setFinished:
using(will|did)ChangeValueForKey:
calls. (I also changed the ivar name to reflect naming prevailing naming conventions). Below is anNSOperation
subclass that I believe accurately models your operation's workings, in terms of finishing in a concurrent fashion.I'm not familiar with your requirements so perhaps you have a pressing need, but your operation would seem a more natural fit for a concurrent operation that uses
start
instead ofmain
. I've implemented a small example that appears to be working correctly.