我已经放弃了NSOperation并设置了我的completionBlock,但即使操作完成,它似乎永远不会进入。这是我的代码:
目录控制器类设置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];
...
}
如您所见,我正在设置完成块以在主线程上执行某个方法,在其他控制器中。
然后,在main
我的子类NSOperation类:ImportWordOperation.m
中,我启动了后台操作。我甚至覆盖了isFinished
iVar,以便触发完成方法:
- (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}];
}
}
}
如果我删除isFinished访问器方法,则会在ImportWordOperation
完成之前调用完成块。
我已经阅读过我发现的代码,它使用了自己的完成块,但是NSOperation子类中的完成块有什么用呢?
任何想法或指向类似解决的情况将不胜感激。
答案 0 :(得分:18)
你有点陷入并发和非并发NSOperation
子类之间的奇怪空间。通常,当您实施main
时,您的操作是非并发的,isFinished
会在YES
退出后立即更改为main
。
但是,您提供了自己的isFinished
实施,并对其进行了编码,以便isFinished
在YES
退出后才会返回main
。这使得您的操作在很多方面开始像并发操作一样 - 至少包括手动发出KVO通知的需要。
问题的快速解决方案是使用setFinished:
调用来实施(will|did)ChangeValueForKey:
。 (我还更改了ivar名称以反映命名主流命名约定)。下面是一个NSOperation
子类,我认为这样可以准确地模拟您的操作的工作方式,以及时方式完成。
@implementation TestOperation {
BOOL _isFinished;
}
- (void)setFinished:(BOOL)isFinished
{
[self willChangeValueForKey:@"isFinished"];
// Instance variable has the underscore prefix rather than the local
_isFinished = isFinished;
[self didChangeValueForKey:@"isFinished"];
}
- (BOOL)isFinished
{
return ([self isCancelled] ? YES : _isFinished);
}
- (void)main
{
NSLog(@"%@ is in main.",self);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
[self setFinished:YES];
});
}
@end
我不熟悉您的要求,因此您可能迫切需要,但对于使用start
而不是main
的并发操作,您的操作似乎更自然。我已经实现了一个似乎正常工作的小例子。
@implementation TestOperation {
BOOL _isFinished;
BOOL _isExecuting;
}
- (void)setFinished:(BOOL)isFinished
{
if (isFinished != _isFinished) {
[self willChangeValueForKey:@"isFinished"];
// Instance variable has the underscore prefix rather than the local
_isFinished = isFinished;
[self didChangeValueForKey:@"isFinished"];
}
}
- (BOOL)isFinished
{
return _isFinished || [self isCancelled];
}
- (void)cancel
{
[super cancel];
if ([self isExecuting]) {
[self setExecuting:NO];
[self setFinished:YES];
}
}
- (void)setExecuting:(BOOL)isExecuting {
if (isExecuting != _isExecuting) {
[self willChangeValueForKey:@"isExecuting"];
_isExecuting = isExecuting;
[self didChangeValueForKey:@"isExecuting"];
}
}
- (BOOL)isExecuting
{
return _isExecuting;
}
- (void)start
{
NSLog(@"%@ is in start. isCancelled = %@", self, [self isCancelled] ? @"YES" : @"NO");
if (![self isCancelled]) {
[self setFinished:NO];
[self setExecuting:YES];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ {
sleep(1);
[self setExecuting:NO];
[self setFinished:YES];
});
}
}
@end
答案 1 :(得分:1)
我在实现NSOperation
的异步子类时遇到了这个错误。
引用关键路径的Swift方式是使用#keyPath
指令,所以我这样做(_executing
和_finished
是我的内部变量):
self.willChangeValue(forKey: #keyPath(Operation.isExecuting))
self._executing = false
self.didChangeValue(forKey: #keyPath(Operation.isExecuting))
self.willChangeValue(forKey: #keyPath(Operation.isFinished))
self._finished = true
self.didChangeValue(forKey: #keyPath(Operation.isFinished))
不幸的是,上面的#keyPath
表达式分别解析为"executing"
和"finished"
,我们需要针对"isExecuting"
和"isFinished"
投放KVO通知。这就是为什么completionBlock
没有被调用。
解决方案是对它们进行硬编码:
self.willChangeValue(forKey: "isExecuting")
self._executing = false
self.didChangeValue(forKey: "isExecuting")
self.willChangeValue(forKey: "isFinished")
self._finished = true
self.didChangeValue(forKey: "isFinished")