NSOperationQueue вътре в NSOperation причинява замразяване на приложението с waitUntilFinished:YES

Имам NSOperation с AFHTTPClient заявка. В края на операцията трябва да изпълня още 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 и това е всичко! Приложението ми напълно замръзва (дори потребителския интерфейс)

След известно проучване решавам, че замръзването е причинено от [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


person striker    schedule 24.05.2014    source източник


Отговори (1)


Причината да блокирате е, че AFNetworking изпраща блоковете за завършване към главната опашка. Следователно waitUntilFinished в този първи success манипулатор ще блокира основната опашка, докато подчинените заявки завършат. Но тези подчинени заявки не могат да завършат, защото трябва да изпратят своите блокове за завършване към главната опашка, която първата операция все още блокира.

Ясно е, че така или иначе никога не искате да блокирате главната опашка, но получавате блокиране, ако блокирате главната опашка, чакаща за операции, които сами по себе си се нуждаят от главната опашка.

person Rob    schedule 24.05.2014
comment
@striker Да, това решение е добре. Тъй като питате обаче, изобщо не съм луд по цялостния дизайн, като една операция чака други (вие заемате една от maxConcurrentOperationCount с операция, която всъщност не прави нищо, освен да чака други). Предполагам, че правите това, защото имате някаква друга операция, която зависи от завършването на всички тези други. Ако е така, бих помислил за персонализиран блоков параметър completionHandler или нещо подобно, вместо да оставя първата операция да чака останалите. - person Rob; 25.05.2014
comment
моята операция всъщност не чака просто да завърши всички други операции. Операция качване на някакъв списък от сървър със структура като [{id:1,updatedAt:2014-05-24 19:55},{id:2,updatedAt:2014-05-23 19:55}] и итерация през тези елементи и if updatedAt ‹ последно актуализирано време (което съхранявам на устройството), трябва да изпълня още една допълнителна заявка, която капсулирам в основната си операция. така че вътрешните операции може дори да не се създават. - person striker; 25.05.2014