Выполнить блок внутри NSOperation

У меня есть метод в каком-то классе, который выполняет какую-то задачу, используя блок. Когда я выполняю этот метод с помощью NSInvocationOperation, управление никогда не переходит к блоку. Я пытался войти в блок, но на самом деле это никогда не вызывается. Но если я просто вызову этот метод с экземпляром этого класса, все будет работать так, как ожидалось.

Разве блоки не запускаются внутри NSOperation?

NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:myClassObj selector:@selector(myClassMethod:) object:obj1];
[[AppDelegate sharedOpQueue] addOperation:op];
[op release];


- (void)myClassMethod:(id)obj
{
    AnotherClass *otherClass = [[AnotherClass allco] init]
    [otherClass fetchXMLWithCompletionHandler:^(WACloudURLRequest* request, xmlDocPtr doc, NSError* error)
     {
         if(error){
             if([_delegate respondsToSelector:@selector(handleFail:)]){
                 [_delegate handleFail:error];
             }
             return;
         }

         if([_delegate respondsToSelector:@selector(doSomeAction)]){
             [_delegate doSomeAction];
         }
     }];

}

- (void) fetchXMLWithCompletionHandler:(WAFetchXMLHandler)block
{
    _xmlBlock = [block copy];
    [NSURLConnection connectionWithRequest:request delegate:self];
}

-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    if(_xmlBlock) {
        const char *baseURL = NULL;
        const char *encoding = NULL;

        xmlDocPtr doc = xmlReadMemory([_data bytes], (int)[_data length], baseURL, encoding, (XML_PARSE_NOCDATA | XML_PARSE_NOBLANKS)); 

        NSError* error = [WAXMLHelper checkForError:doc];

        if(error){
            _xmlBlock(self, nil, error);
        } else {
            _xmlBlock(self, doc, nil);
        }

        xmlFreeDoc(doc);
    }
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    if(_xmlBlock) {
        _xmlBlock(self, nil, error);
    }
}

person Jitendra Singh    schedule 19.11.2011    source источник
comment
Является ли метод -parseXMLWithCompletionHandler: в AnotherClass асинхронным? Возможно, вы захотите опубликовать код для этого метода, так как похоже, что все, что он делает для обратного вызова, не вызывается, и это не описано в приведенном выше коде.   -  person Brad Larson    schedule 19.11.2011
comment
@BradLarson спасибо за ответ, я отредактировал вопрос с подробностями метода -parseXMLWithCompletionHandler:   -  person Jitendra Singh    schedule 20.11.2011
comment
Ваш код вызывает parseXMLWithCompletionHandler:, но показывает реализацию для fetchXMLWithCompletionHandler:.   -  person titaniumdecoy    schedule 21.11.2011
comment
@Bavarious Я не добавлял тег блока. Я просто заменяю тег iOS.   -  person vikingosegundo    schedule 21.11.2011
comment
@vikingosegundo Я знаю; это просто примечание: когда вы редактируете вопросы, не стесняйтесь также заменять блок/блоки на Objective-C-блоки!   -  person    schedule 23.11.2011
comment
хорошо, в большинстве случаев я просто сосредотачиваюсь на раздражающей iOS и самом раздражающем теге Xcode   -  person vikingosegundo    schedule 23.11.2011
comment
@vikingosegundo спасибо за исправление тегов   -  person Jitendra Singh    schedule 23.11.2011
comment
@titaniumdecoy Я исправил звонок   -  person Jitendra Singh    schedule 23.11.2011


Ответы (2)


Вы выполняете NSConnection асинхронно (что вам не нужно делать в NSOperation, потому что вы уже должны быть в фоновом потоке).

После вашего вызова fetchXMLWithCompletionHandler ваш метод завершается. Это сигнализирует о том, что NSOperation завершена, и она освобождается, а ее поток либо повторно используется для чего-то другого, либо, что более вероятно, также освобождается. Это означает, что к тому времени, когда вы получите свои обратные вызовы, ваш первоначальный объект больше не существует!

Есть два решения:

1) Используйте NSURLConnection синхронно. Это будет ждать в вашем myClassMethod, пока не получит ответ.

2) Узнайте о параллельный режим NSOperations. Я не знаю, будет ли это работать с NSInvocationOperation :( И это довольно сложно по сравнению с вариантом (1).

Я бы использовал метод (1) — вы уже создали фоновый поток для выполнения своей операции, зачем создавать еще один для выполнения вашего запроса на подключение?

person deanWombourne    schedule 23.11.2011

Есть два способа решить вашу проблему:

Легкий выход

— как предполагает Дин — использует +[NSURLConnection sendSynchronousRequest:returningResponse:error:], поскольку вы уже находитесь в другом потоке. Это помогло вам — я бы сказал — в 80-90% случаев, очень просто реализовать и Just Works™.

Другой способ

лишь немного сложнее, и вы рассмотрели все случаи, когда первого метода недостаточно, посетив корень вашей проблемы:

NSURLConnection работает в сочетании с циклом выполнения, а потоки, управляемые NSOperationQueue, не обязательно используют (или даже имеют!) связанный цикл выполнения.

Хотя вызов +[NSURLConnection connectionWithRequest:delegate:] неявно создает цикл выполнения, если это необходимо, это не приводит к фактическому запуску цикла выполнения!

Это ваша ответственность, если NSOperationQueue вы используете не очередь, связанную с основным потоком.

Для этого измените свою реализацию fetchXMLWithCompletionHandler:, чтобы она выглядела примерно так:

- (void)fetchXMLWithCompletionHandler:(WAFetchXMLHandler)block
{
    self.xmlHandler = block; // Declare a @property for the block with the copy attribute set

    self.mutableXMLData = [NSMutableData data]; // again, you should have a property for this...

    self.currentConnection = [NSURLConnection connectionWithRequest:request delegate:self]; // having a @property for the connection allows you to cancel it, if needed.

    self.connectionShouldBeRunning = YES; // ...and have a BOOL like this one, setting it to NO in connectionDidFinishLoad: and connection:didFailWithError:

    NSRunLoop *loop = [NSRunLoop currentRunLoop];
    NSDate *neverExpire = [NSDate distantFuture];

    BOOL runLoopDidIterateRegularly = YES;
    while( self.connectionShouldBeRunning && runLoopDidIterateRegularly ) {
        runLoopDidIterateRegularly = [loop runMode:NSDefaultRunLoopMode beforeDate:neverExpire];
    }
}

С этими небольшими изменениями все готово. Бонус: это действительно гибко и (в конечном итоге) повторно используется во всем вашем коде — если вы переместите анализ XML из этого класса и заставите свой обработчик просто принимать NSData, NSError и (необязательно) NSURLResponse.

Поскольку вы, вероятно, не хотите, чтобы клиенты вашего загрузчика видели свойства, которые я только что предложил вам добавить, и, возможно, возились с ними, вы можете объявить их в продолжении класса.

person danyowdee    schedule 27.11.2011