Cómo implementar un NSRunLoop dentro de una NSOperation

Estoy publicando esta pregunta porque he visto mucha confusión sobre este tema y como resultado pasé varias horas depurando subclases de NSOperation.

El problema es que NSOperation no le ayuda mucho cuando ejecuta métodos asíncronos que no sonen realidad completa Hasta que se complete la devolución de llamada asíncrona.

Si la propia NSOperation es el delegado de devolución de llamada, es posible que ni siquiera sea suficiente para completar correctamente la operación debido a que la devolución de llamada se produce en un subproceso diferente.

Digamos que está en el hilo principal y crea una NSOperation y la agrega a una NSOperationQueue. El código dentro de la NSOperation dispara una llamada asíncrona que vuelve a llamar a algún método en el AppDelegate o un controlador de vista.

No puedes bloquear el hilo principal o la IU se bloqueará, así que tienes dos opciones.

1) Cree una NSOperation y agréguela a NSOperationQueue con la siguiente firma:

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

Buena suerte con eso. Las operaciones asíncronas generalmente requieren un runloop, por lo que no funcionarán a menos que subclasifique NSOperation o use un bloque, pero incluso un bloque no funcionará si tiene que "completar" la NSOperation indicándole cuándo finalizó la devolución de llamada.

Entonces ... subclase NSOperation con algo similar a lo siguiente para que la devolución de llamada pueda indicar la operación cuando haya finalizado:

//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;
}

De acuerdo, esto no funciona, ¡lo hemos eliminado!

2) El segundo problema con este método es que digamos que lo anterior realmente funcionó (que no lo hace) en los casos en que los runLoops tienen que terminar correctamente, (o en realidad terminan desde una llamada de método externa en una devolución de llamada)

Asumamos un segundo Im en el subproceso principal cuando llamo a esto, a menos que quiera que la interfaz de usuario se bloquee un poco y no pinte nada, no puedo decir "waitUntilFinished: YES" en el método NOperationQueue addOperation ...

Entonces, ¿cómo puedo lograr el mismo comportamiento que waitUntilFinished: YES sin bloquear el hilo principal?

Ya que hay tantas preguntas sobre el comportamiento de RunLoops, NSOperationQueues y Asynch en Cocoa, publicaré mi solución como respuesta a esta pregunta.

Tenga en cuenta que solo estoy respondiendo a mi propia pregunta porque verifiqué meta.stackoverflow y dijeron que esto es aceptable y alentador, esperola respuesta que sigue ayuda a las personas a comprender por qué sus runloops se están bloqueando en las operaciones NSO y cómo pueden completar adecuadamente las operaciones NSO a partir de devoluciones de llamada externas. (Callbacks en otros hilos)

Respuestas a la pregunta(3)

Su respuesta a la pregunta