【问题标题】:Why my completionBlock never gets called in an NSOperation?为什么我的完成块永远不会在 NSOperation 中被调用?
【发布时间】:2013-03-26 04:59:43
【问题描述】:

我已经子化了一个 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 子类中的完成块有什么用呢?

任何想法或指向类似的已解决情况将不胜感激。

【问题讨论】:

    标签: iphone ios6 nsoperation


    【解决方案1】:

    您在这里有点陷入并发和非并发NSOperation 子类之间的奇怪空间。通常,当您实现main 时,您的操作是非并发的,一旦main 退出,isFinished 就会更改为YES

    但是,您已经提供了自己的isFinished 实现,并且还对其进行了编码,以便isFinishedmain 退出之前不会返回YES。这使您的操作在许多方面开始像并发操作一样 - 至少包括需要手动发出 KVO 通知。

    您的问题的快速解决方案是使用(will|did)ChangeValueForKey: 调用实现setFinished:。 (我还更改了 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
    

    【讨论】:

    • 谢谢!这是main/start 方法。我需要实现start 而不是main,因为我需要它是并发的。我改变了它并在 setFinished 中调整了 KVO,它起作用了!我正在添加 -cancel 方法来四舍五入。
    【解决方案2】:

    我在实现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")
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-22
      • 2018-11-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-15
      相关资源
      最近更新 更多