Como implementar um NSRunLoop dentro de uma NSOperation

Estou postando essa pergunta porque tenho visto muita confusão sobre esse tópico e passei várias horas depurando subclasses de NSOperation como resultado.

O problema é que o NSOperation não faz muito bem quando você executa métodos assíncronos que não sãona verdade completa até que o retorno de chamada assíncrono seja concluído.

Se o NSOperation propriamente dito for o delegado de retorno de chamada, pode não ser suficiente para concluir a operação adequadamente devido ao retorno de chamada que ocorre em um thread diferente.

Vamos dizer que você está no thread principal e criar um NSOperation e adicioná-lo a um NSOperationQueue, o código dentro do NSOperation dispara uma chamada assíncrona que chama de volta para algum método no AppDelegate ou em um controlador de exibição.

Você não pode bloquear o thread principal ou a interface do usuário irá bloquear, então você tem duas opções.

1) Crie uma NSOperation e adicione-a ao NSOperationQueue com a seguinte assinatura:

[NSOperationQueue addOperations: @ [myOp] waitUntilFinished:?]

Boa sorte com isso. As operações assíncronas geralmente requerem um runloop, portanto, ele não funcionará a menos que você subclasse NSOperation ou use um bloco, mas mesmo um bloco não funcionará se você tiver que "concluir" o NSOperation informando quando o retorno de chamada foi concluído.

Então ... você subclasse NSOperation com algo parecido com o seguinte, para que o callback possa informar a operação quando terminar:

//you create an NSOperation subclass it includes a main method that
//keeps the runloop going as follows
//your NSOperation subclass has a BOOL field called "complete"

-(void) main
{

    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];

    //I do some stuff which has async callbacks to the appDelegate or any other class (very common)

    while (!complete && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);

}

//I also have a setter that the callback method can call on this operation to 
//tell the operation that its done, 
//so it completes, ends the runLoop and ends the operation

-(void) setComplete {
    complete = true;
}

//I override isFinished so that observers can see when Im done
// - since my "complete" field is local to my instance

-(BOOL) isFinished
{
    return complete;
}

OK - Isso absolutamente não funciona - nós tiramos isso do caminho!

2) O segundo problema com este método é que vamos dizer que o acima realmente funcionou (o que não acontece) nos casos em que runLoops tem que terminar corretamente, (ou realmente terminar de uma chamada de método externo em um retorno de chamada)

Vamos supor por um segundo Im no segmento principal quando eu chamo isso, a menos que eu quero a interface do usuário para bloquear um pouco, e não pintar nada, eu não posso dizer "waitUntilFinished: YES" no método addOperation NSOperationQueue ...

Então, como eu realizo o mesmo comportamento que waitUntilFinished: YES sem bloquear o thread principal?

Como há tantas perguntas sobre o comportamento de runLoops, NSOperationQueues e Asynch no Cocoa, postarei minha solução como uma resposta a essa pergunta.

Note que estou apenas respondendo a minha própria pergunta, porque eu verifiquei meta.stackoverflow e eles disseram que isso é aceitável e incentivado, esperoa resposta que segue ajuda as pessoas a entender por que seus runloops estão sendo bloqueados em NSOperations e como eles podem concluir adequadamente NSOperations a partir de retornos de chamada externos. (Retornos de chamada em outros segmentos)

questionAnswers(3)

yourAnswerToTheQuestion