NSOperation内部的NSOperationQueue导致应用程序冻结,使用waitUntilFinished:YES

时间:2014-05-24 05:30:24

标签: objective-c nsoperation nsoperationqueue

我对AFHTTPClient请求有NSOperation。在操作结束时,我需要对请求执行另外N个操作,并等待请求完成以将主操作标记为已完成

@interface MyOperation : OBOperation

@end

@implementation MyOperation

- (id)init
{
    if (self = [super init]) {
        self.state = OBOperationReadyState;
    }

    return self;
}

- (void)start
{
    self.state = OBOperationExecutingState;

    AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:@"http://google.com"]];
    [client getPath:@"/"
         parameters:nil
            success:^(AFHTTPRequestOperation *operation, id responseObject) {
                NSOperationQueue *queue = [NSOperationQueue new];
                queue.maxConcurrentOperationCount = 1;

                NSMutableArray *ops = [NSMutableArray array];
                for (int i = 1; i < 10; i++) {
                    MyInnerOperation *innerOp = [[MyInnerOperation alloc] initWithNumber:@(i)];
                    [ops addObject:innerOp];
                }

                [queue addOperations:ops waitUntilFinished:YES];

                self.state = OBOperationFinishedState;
            } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
                self.state = OBOperationFinishedState;
                NSLog(@"error");
            }];
}

@end

在问题结尾处链接到OBOperation来源。这是一个简单的类,它添加了有用的方法来控制NSOperation流程

内部操作样本:

@interface MyInnerOperation : OBOperation

- (id)initWithNumber:(NSNumber *)number;

@end

@implementation MyInnerOperation

- (id)initWithNumber:(NSNumber *)number
{
    if (self = [super init]) {
        _number = number;
        self.state = OBOperationReadyState;
    }

    return self;
}

- (void)start
{
    self.state = OBOperationExecutingState;

    NSLog(@"begin inner operation: %@", _number);

    AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:@"http://google.com"]];
    [client getPath:@"/"
         parameters:nil
            success:^(AFHTTPRequestOperation *operation, id responseObject) {
                NSLog(@"inner operation success: %@", _number);
                self.state = OBOperationFinishedState;
            } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
                self.state = OBOperationFinishedState;
                NSLog(@"inner operation error: %@", _number);
            }];
}

@end

所以,如果我开始操作:

MyOperation *op = [MyOperation new];
[_queue addOperation:op];

我在控制台begin inner operation: 1中看到了这一切!我的应用程序完全冻结(甚至UI)

经过一番探索后,我决定由[queue addOperations:ops waitUntilFinished:YES];造成冻结。如果我不等待完成,我的内部操作按预期工作,但MyOperation在子操作完成之前完成。

所以现在我有依赖块操作的解决方法:

NSBlockOperation *endOperation = [NSBlockOperation blockOperationWithBlock:^{
    self.state = OBOperationFinishedState;
}];

NSMutableArray *ops = [NSMutableArray arrayWithObject:endOperation];
for (int i = 1; i < 10; i++) {
    MyInnerOperation *innerOp = [[MyInnerOperation alloc] initWithNumber:@(i)];
    [ops addObject:innerOp];

    [endOperation addDependency:innerOp];
}

[queue addOperations:ops waitUntilFinished:NO];

但我仍然完全不明白这个冻结的真正问题是什么。任何解释都非常有用。

OBOperaton类来源:https://dl.dropboxusercontent.com/u/1999619/issue/OBOperation.h https://dl.dropboxusercontent.com/u/1999619/issue/OBOperation.m

整个项目:https://dl.dropboxusercontent.com/u/1999619/issue/OperationsTest.zip

1 个答案:

答案 0 :(得分:3)

您遇到死锁的原因是AFNetworking将完成块调度到主队列。因此,第一个waitUntilFinished处理程序中的success将阻止主队列,直到下级请求完成。但是那些从属请求无法完成,因为他们需要将完成块分配给主队列,第一个操作仍在阻塞。

显然,你永远不想阻塞主队列,但是如果你阻塞等待操作的主队列就会收到死锁,这些操作本身就需要主队列。