iphone nsoperation nsoperationqueue performselector

iphone - Subclasificar NSOperation para ser concurrente y cancelable



nsoperationqueue performselector (6)

Bien, como lo entiendo, tienes dos preguntas:

  1. ¿Necesita el segmento performSelectorOnMainThread: que aparece en los comentarios en su código? ¿Qué hace ese código?

  2. ¿Por qué el indicador _isCancelled no se modifica cuando llama a cancelAllOperations en el NSOperationQueue que contiene esta operación?

Vamos a tratar con estos en orden. Voy a suponer que su subclase de NSOperation se llama MyOperation , solo para facilitar la explicación. Explicaré lo que estás entendiendo mal y luego daré un ejemplo corregido.

1. Ejecutando NSOperaciones concurrentemente

La mayoría de las veces, NSOperation s con un NSOperationQueue , y desde tu código, parece que eso es lo que estás haciendo. En ese caso, MyOperation siempre se ejecutará en un subproceso en segundo plano, independientemente de lo que el método -(BOOL)isConcurrent devuelve, ya que los NSOperationQueue s están diseñados explícitamente para ejecutar operaciones en segundo plano.

Como tal, generalmente no es necesario anular el método -[NSOperation start] , ya que de manera predeterminada simplemente invoca el método -main . Ese es el método que debes anular. El método predeterminado de -start ya maneja la configuración isExecuting y isFinished para usted en los momentos apropiados.

Entonces, si desea que una NSOperation ejecute en segundo plano, simplemente anule el método -main y póngalo en una NSOperationQueue .

El performSelectorOnMainThread: en su código causaría que cada instancia de MyOperation siempre realice su tarea en el hilo principal. Dado que solo se puede ejecutar un fragmento de código en un subproceso a la vez, esto significa que no se pueden ejecutar otras MyOperation s. Todo el propósito de NSOperation y NSOperationQueue es hacer algo en segundo plano.

La única vez que quiere forzar cosas en el hilo principal es cuando está actualizando la interfaz de usuario. Si necesita actualizar la interfaz de usuario cuando MyOperation su MyOperation , es cuando debe usar performSelectorOnMainThread: Voy a mostrar cómo hacerlo en mi ejemplo a continuación.

2. Cancelando una NSOperation

-[NSOperationQueue cancelAllOperations] llama al método -[NSOperation cancel] , lo que hace que las llamadas subsiguientes a -[NSOperation isCancelled] devuelvan YES . Sin embargo , has hecho dos cosas para hacer esto inefectivo.

  1. Está utilizando @synthesize isCancelled para anular el método -isCancelled de -isCancelled . No hay razón para hacer esto. NSOperation ya implementa -isCancelled de una manera perfectamente aceptable.

  2. Está comprobando su propia variable de instancia _isCancelled para determinar si la operación se ha cancelado. NSOperation garantiza que [self isCancelled] devolverá YES si la operación se ha cancelado. No garantiza que se llamará a su método de establecimiento personalizado, ni que su propia variable de instancia esté actualizada. Debes estar marcando [self isCancelled]

Que deberias estar haciendo

El encabezado:

// MyOperation.h @interface MyOperation : NSOperation { } @end

Y la implementación:

// MyOperation.m @implementation MyOperation - (void)main { if ([self isCancelled]) { NSLog(@"** operation cancelled **"); } // Do some work here NSLog(@"Working... working....") if ([self isCancelled]) { NSLog(@"** operation cancelled **"); } // Do any clean-up work here... // If you need to update some UI when the operation is complete, do this: [self performSelectorOnMainThread:@selector(updateButton) withObject:nil waitUntilDone:NO]; NSLog(@"Operation finished"); } - (void)updateButton { // Update the button here } @end

Tenga en cuenta que no necesita hacer nada con isExecuting , isCancelled o isFinished . Esos son todos manejados automáticamente para usted. Simplemente anule el método -main . Es fácil.

(Una nota: técnicamente, esto no es una NSOperation "concurrente", en el sentido de que -[MyOperation isConcurrent] devolvería NO como se implementó anteriormente. Sin embargo, se ejecutará en un subproceso en segundo plano. El método isConcurrent realmente debería llamarse -willCreateOwnThread , ya que es una descripción más precisa de la intención del método.)

No puedo encontrar una buena documentación sobre cómo subclasificar NSOperation para ser concurrente y también para admitir la cancelación. Leí los documentos de Apple, pero no puedo encontrar un ejemplo "oficial".

Aquí está mi código fuente:

@synthesize isExecuting = _isExecuting; @synthesize isFinished = _isFinished; @synthesize isCancelled = _isCancelled; - (BOOL)isConcurrent { return YES; } - (void)start { /* WHY SHOULD I PUT THIS ? if (![NSThread isMainThread]) { [self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO]; return; } */ [self willChangeValueForKey:@"isExecuting"]; _isExecuting = YES; [self didChangeValueForKey:@"isExecuting"]; if (_isCancelled == YES) { NSLog(@"** OPERATION CANCELED **"); } else { NSLog(@"Operation started."); sleep(1); [self finish]; } } - (void)finish { NSLog(@"operationfinished."); [self willChangeValueForKey:@"isExecuting"]; [self willChangeValueForKey:@"isFinished"]; _isExecuting = NO; _isFinished = YES; [self didChangeValueForKey:@"isExecuting"]; [self didChangeValueForKey:@"isFinished"]; if (_isCancelled == YES) { NSLog(@"** OPERATION CANCELED **"); } }

En el ejemplo que encontré, no entiendo por qué se usa performSelectorOnMainThread: Evitaría que mi operación se ejecute simultáneamente.

Además, cuando comento esa línea, hago que mis operaciones se ejecuten simultáneamente. Sin embargo, la bandera isCancelled no se modifica, aunque haya llamado a cancelAllOperations .


Con respecto a definir la propiedad " cancelada " (o definir " _cancelled " iVAR) dentro de la subclase NSOperation, normalmente NO es necesario. Simplemente porque cuando el USUARIO activa la cancelación, el código personalizado siempre debe notificar a los observadores de KVO que su operación ya ha terminado con su trabajo. En otras palabras, isCancelled => isFinished.

En particular, cuando el objeto NSOperation depende de la finalización de otros objetos de operación, supervisa la ruta de acceso de clave finalizada para esos objetos. No generar una notificación de finalización ( en caso de que ocurra una cancelación ) puede evitar la ejecución de otras operaciones en su aplicación.

Por cierto, la respuesta de @BJ Homer: "El método isConcurrent realmente debería llamarse -willCreateOwnThread" ¡tiene MUCHO sentido !

Porque si NO anula el método de inicio, simplemente llame manualmente al método de inicio por defecto de NSOperation-Object, el propio hilo de llamada es, de forma predeterminada, síncrono; por lo tanto, NSOperation-Object es solo una operación no concurrente.

Sin embargo, si NO reemplaza el método de inicio, dentro de la implementación del método de inicio, el código personalizado debería generar un subproceso separado ... etc., entonces rompe con éxito la restricción de que "el valor predeterminado del subproceso de llamada sea sincrónico", por lo tanto, el NSOperation-Object se convierte operación concurrente, puede ejecutarse de forma asíncrona después.


Echa un vistazo a ASIHTTPRequest . Es una clase de envoltorio HTTP construida sobre NSOperation como una subclase y parece implementar esto. Tenga en cuenta que a partir de mediados de 2011, el desarrollador recomienda no utilizar ASI para nuevos proyectos.



La excelente respuesta de @BJHomer merece una actualización.

Las operaciones concurrentes deben anular el método de start lugar de main .

Como se indica en la documentación de Apple :

Si está creando una operación concurrente, necesita anular los siguientes métodos y propiedades como mínimo:

  • start
  • asynchronous
  • executing
  • finished

Una implementación adecuada también requiere anular la cancel también. Hacer que una subclase sea segura para el subproceso y obtener la semántica correcta es también bastante complicado.

Por lo tanto, he puesto una subclase completa y de trabajo como una propuesta implementada en Swift en Code Review. Comentarios y sugerencias son bienvenidos.

Esta clase se puede usar fácilmente como una clase base para su clase de operación personalizada.


Sé que esta es una pregunta antigua, pero he estado investigando esto últimamente y encontré los mismos ejemplos y tuve las mismas dudas.

Si todo su trabajo se puede ejecutar de forma síncrona dentro del método principal, no necesita una operación simultánea, ni un inicio que invalida, simplemente haga su trabajo y regrese del principal cuando haya terminado.

Sin embargo, si su carga de trabajo es asíncrona por naturaleza, es decir, al cargar un NSURLConnection, debe iniciarse la subclase. Cuando su método de inicio vuelve, la operación aún no ha finalizado. NSOperationQueue solo lo considerará finalizado cuando envíe manualmente notificaciones de KVO a los indicadores isFinished y isExecuting (por ejemplo, una vez que la carga asíncrona de la URL termine o falle).

Por último, es posible que desee enviar un inicio al subproceso principal cuando la carga de trabajo asíncrona que desea iniciar requiera una escucha en bucle de ejecución en el subproceso principal. Como el trabajo en sí es asíncrono, no limitará su concurrencia, pero el inicio del trabajo en un subproceso de trabajo podría no tener listo un runloop adecuado.