ARC: enviar nil a un object no llama a su dealloc inmediatamente

Estoy pasando de la gestión de la memory manual a ARC y tengo un problema. La mayoría de las veces, estoy realizando la carga de datos de forma asíncrona llamando a performSelectorInBackground en mis classs model. La cuestión es que debo detener cualquier ejecución de código de model cuando el model recibe nil (versión). En non-arc, todo era sencillo: tan pronto como un usuario cierra la window, su controller comienza a desasignarse y desasigna su model [_myModel release], y el model detiene su ejecución de código (carga de datos) y recibe el nombre de método dealloc .

Esto parece ser diferente en ARC. El model todavía ejecuta el código incluso después de recibir ningún post del controller. Su método dealloc se llama después de su ejecución de código (carga de datos) solamente. Este es un problema porque la ejecución del código debería detenerse lo antes posible cuando un usuario cierra la window (controller). Es una especie de falta de control sobre el código, el controller le dice a la model: "váyase, ya no necesito su trabajo", pero el model todavía "está trabajando para terminar su trabajo" :).

Imagine que un model realiza un procesamiento de datos muy pesado con una duración de 10 segundos. Un model comienza a hacer su procesamiento cuando un usuario abre la window (controller). Pero la image de un usuario cambia de opinión y cierra la window, justo después de abrirla. El model aún realiza un procesamiento derrochador. ¿Alguna idea de cómo solucionar o solucionar eso? No me gusta una idea tener una propiedad BOOL "shouldDealloc" especial en mi model y establecerla en YES en el método dealloc controller, y usarla en las condiciones de mi class model. ¿Hay una solución más elegante?

He hecho un proyecto de demostración para mostrar el problema. Para probar solo cree una aplicación de vista única y pegue el código. Cree en botones- "Comenzar calcular" y "Detener calcular" en el file ViewController.xib, y conectar sus IBActions con startCalculationPressed y stopCalculationPressed :

ViewController.h

#import "MyModel.h" @interface ViewController : UIViewController <MyModelDelegate> - (IBAction)startCalculationPressed:(id)sender; - (IBAction)stopCalculationPressed:(id)sender; @end 

ViewController.m

 @interface ViewController (){ __strong MyModel *_myModel; } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (void)didCalculated { NSLog(@"Did calculated..."); } - (IBAction)startCalculationPressed:(id)sender { NSLog(@"Starting to calculate..."); _myModel = nil; _myModel = [[MyModel alloc] init]; _myModel.delegate = self; [_myModel calculate]; } - (IBAction)stopCalculationPressed:(id)sender { NSLog(@"Stopping calculation..."); _myModel.delegate = nil; _myModel = nil; } @end 

Agregue una nueva class MyModel al proyecto:

MyModel.h

 @protocol MyModelDelegate <NSObject> - (void)didCalculated; @end @interface MyModel : NSObject @property (nonatomic, weak) id<MyModelDelegate> delegate; - (void)calculate; @end 

MyModel.m

 @implementation MyModel - (void)dealloc { NSLog(@"MyModel dealloc..."); } - (void)calculate { [self performSelectorInBackground:@selector(performCalculateAsync) withObject:nil]; } - (void)performCalculateAsync { // Performing some longer running task int i; int limit = 1000000; NSMutableArray *myList = [[NSMutableArray alloc] initWithCapacity:limit]; for (i = 0; i < limit; i++) { [myList addObject:[NSString stringWithFormat:@"Object%d", i]]; } [self performSelectorOnMainThread:@selector(calculateCallback) withObject:nil waitUntilDone:NO]; } - (void)calculateCallback { [self.delegate didCalculated]; } @end 

ACTUALIZACIÓN Martin tiene razón, performSelectorOnMainThread siempre se conserva, por lo que no hay forma de detener la ejecución del código en otro subprocess (tanto en ARC como en non-ARC), por lo que no se llama a dealloc inmediatamente al liberar el model. Entonces, eso debería hacerse explícitamente usando la propiedad apropiada (por ejemplo, delegado) con verificación condicional.

Un object se desasigna si su count de lanzamiento se networkinguce a cero, o en lenguaje ARC, si la última reference fuerte a ese object desaparece.

 [self performSelectorInBackground:@selector(performCalculateAsync) withObject:nil]; 

agrega una fuerte reference a self , lo que explica por qué el object no se desasigna antes de que el hilo de background haya finalizado.

No hay forma (que yo sepa) de hacer que un hilo de background se detenga "automáticamente". Lo mismo es cierto para los bloques que se inician con dispatch_async() o para NSOperation . Una vez iniciado, el subprocess / bloque / operación debe supervisar alguna propiedad en los puntos donde está guardado para detenerlo.

En su ejemplo, usted podría monitorearse. Si eso se vuelve nil , a nadie le interesa más el resultado, por lo que el hilo de background puede volver. En ese caso, tendría sentido declarar la propiedad delegate como atomic .

Tenga en count que self.delegate también se establece automáticamente en nil si el controller de vista se desasigna (porque es una propiedad débil) incluso si no se ha llamado stopCalculationPressed .