操作完成后,不会调用nsoperationqueue上的observeValueForKeyPath

时间:2014-10-30 18:03:46

标签: ios objective-c nsoperation nsoperationqueue

我有一个findBusinessOperationQueue,为了知道剩余多少nsoperation,我根据operations添加了@Oserver:

- (id)init {
       if (self = [super init]) {
           self.findBusinessOperationQueue = [NSOperationQueue new];
           [self.getBusinessOperationQueue addObserver:self
                                             forKeyPath:@"operations"
                                               options:0
                                                context:nil];

        }
        return self;
    }
 - (void)observeValueForKeyPath:(NSString *)keyPath
                              ofObject:(id)object
                                change:(NSDictionary *)change
                               context:(void *)context {
              if ([object isEqual:self.findBusinessOperationQueue] && [keyPath isEqualToString:@"operations"])
                NSLog(@"        ======> FindBusinessoperationQueue size: %lu", (unsigned long)[[self.findBusinessOperationQueue operations] count]);

            else
                [super observeValueForKeyPath:keyPath
                                     ofObject:object
                                       change:change
                                      context:context];
        }

nsoperations添加到其中后:

FindBusinessOperation *op = [[FindBusinessOperation alloc] init];
[self.findBusinessOperationQueue addOperation:op];

我在控制台上得到了我的期望:

======> FindBusinessOperationQueue size: 1

在FindBusinessOperation,我使用NSURLSessionDataTask从服务器下载一些数据(在这种情况下,我下载了pdf文件。

APPROACH 1 :使用不带完成块的NSURLSessionDataTask

-(void)start {
    // Dont do any downloading if op is cancelled
    if (self.cancelled)
        return;

    NSLog(@"                    FindBusiness START :main thread = %d",[NSThread isMainThread]);

    // Change to isExecuting status
    [self willChangeValueForKey:NSLocalizedString(@"isExecuting", nil)];
    opExecuting = YES;
    [self didChangeValueForKey:NSLocalizedString(@"isExecuting", nil)];


    NSURLSessionDataTask *task = [self.session dataTaskWithURL: [NSURL URLWithString:@"http://cdn.oreillystatic.com/oreilly/booksamplers/9781449359348_sampler.pdf"]];

    [task resume];
}
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {

    completionHandler(NSURLSessionResponseAllow);

    downloadSize=[response expectedContentLength];
    dataToDownload=[[NSMutableData alloc]init];
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    [dataToDownload appendData:data];
    CGFloat dataDownloaded  = [dataToDownload length ]/downloadSize ;
    NSLog(@"----> is %f",dataDownloaded);
    if ( (int)dataDownloaded == 1 )
        [self opCompleted];

}

-(BOOL)isConcurrent {
    return YES;
}
- (BOOL)isOpExecuting {
    return opExecuting;
}

- (BOOL)isFinished {
    return opCompleted;
}

-(void)opCompleted {
    [self willChangeValueForKey:NSLocalizedString(@"isExecuting", nil)];
    [self willChangeValueForKey:NSLocalizedString(@"isFinished", nil)];

    opExecuting = NO;
    opCompleted = YES;

    [self didChangeValueForKey:NSLocalizedString(@"isExecuting", nil)];
    [self didChangeValueForKey:NSLocalizedString(@"isFinished", nil)];
}

正如我们所见,当我们完成下载所有数据时会调用opCompleted,它也会通知(BOOL)isFisnied。从理论上讲,这个nsoperation将从findBusinessOperationQueue中移除,我们预计队列中的剩余操作为0.

不幸的是,observeValueForKeyPath在进程结束时没有被调用。现在还在苦苦挣扎。

APPROACH 2 使用NSURLSessionDataTask和completionBlock,如下所示

NSURLSessionDataTask *task = [self.session dataTaskWithURL:[NSURL URLWithString:@"http://cdn.oreillystatic.com/oreilly/booksamplers/9781449359348_sampler.pdf"]
                                             completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                                                 NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
                                                 self.statusCode = httpResponse.statusCode;
                                                 if (self.statusCode == 200)
                                                         NSLog(@"DONE ");                                                       
                                                     else
                                                         NSLog(@"FAILED ");

                                                     NSLog(@"FINDBUSINESS DONE. S:%.2f MB", (float)data.length/1024.0f/1024.0f);
                                                     [self opCompleted];
                                                     self.resultBlock([summary.business allObjects]);
                                                 }
                                             }
                                  ];


    [task resume];

然后它起作用

   2014-10-30 13:53:35.544 Discovery[1994:485268]         ======> FindBusinessoperationQueue size: 1
   2014-10-30 13:53:45.472 Discovery[1994:485268]                     FINDBUSINESS DONE. #:0  S:6.97 M
   2014-10-30 13:53:50.588 Discovery[1994:485268]         ======> FindBusinessoperationQueue size: 0

问题:为什么后一种方法不起作用?

如果您之前遇到此问题,请给我一个提示,欢迎所有答案。

1 个答案:

答案 0 :(得分:0)

我知道这是一个老问题,但我会戴上帽子,看看能不能帮忙。实际上这里有很多内容,但有些内容在您的代码片段中并不清楚,因为我们实际上并没有全面了解。

首先,您没有明确说出来,但是有没有理由让您将FindBusinessOperation并发?操作通常在操作队列队列中进行,队列将始终在后台线程上执行操作。如果您正在观察操作并且您的观察者需要符合KVO,则只需要使操作并发,并向观察者报告有关其排队操作的状态更改。请参阅Configuring Operations for Concurrent ExecutionKVO Compliance

其次,在您的观察课中,您是否对FindBusinessOperation操作有强烈的引用?它看起来不像你,因此当队列使你完成的操作出列时,系统可能将操作设置为nil(然后你的队列的操作数组可能会设置为nil)。当观察者被设置为零时,我认为KVO不会发送通知。

您也可以考虑使用keyPath @“operationCount”而不是@“operations”注册您的观察者,因为您的设置中的多对多关系可能存在问题。见To-Many Relationships

您也可以在改变利用NSOperation's setCompletionBlock: method的方法方面取得成功。在完成块中,您可以检查队列的operationCount - 您可能需要考虑同步以及保留指向队列实例的指针的块。 您也可以使用此完成块来触发操作的最终KVO通知。

如果没有完整的资料来源,我恐怕无法确切地说明为什么第一种方法失败而第二种方法没有。