2013-04-13 5 views
8

Sto passando dalla gestione manuale della memoria ad ARC e ho un problema. La maggior parte delle volte eseguo il caricamento dei dati in modo asincrono chiamando performSelectorInBackground nelle mie classi modello. Il fatto è che ho bisogno di interrompere qualsiasi esecuzione del codice modello quando il modello riceve nil (rilascio). In non-arc, tutto era semplice: non appena un utente chiude la finestra, il suo controllore inizia a deallocarsi e rilascia il suo modello [_myModel release], e così il modello interrompe l'esecuzione del codice (caricamento dei dati) e viene chiamato il suo metodo dealloc .ARC: inviare nil a un oggetto non chiama immediatamente il suo dealloc

Questo sembra essere diverso in ARC. Il modello esegue ancora il codice anche dopo aver ricevuto il messaggio nullo dal controller. Il suo metodo dealloc viene chiamato solo dopo l'esecuzione del codice (caricamento dati). Questo è un problema perché l'esecuzione del codice dovrebbe fermarsi al più presto quando un utente chiude la finestra (controller). È una sorta di mancanza di controllo sul codice - il controller dice al modello - "vai via, non ho più bisogno del tuo lavoro" ma il modello continua a "funzionare per completare il suo lavoro" :).

Immaginate che un modello esegua un'elaborazione dei dati molto pesante con una durata di 10 secondi. Un modello inizia a eseguire l'elaborazione quando un utente apre la finestra (controller). Ma l'immagine di un utente cambia idea e chiude la finestra, subito dopo l'apertura. Il modello esegue ancora una lavorazione dispendiosa. Qualche idea su come risolvere o risolvere questo? Non mi piace l'idea di avere una proprietà BOOL "shouldDealloc" speciale nel mio modello e impostata su SÌ nel metodo dealloc del controller e utilizzare nelle condizioni della mia classe modello. C'è una soluzione più elegante?

Ho fatto qualche progetto dimostrativo per mostrare il problema. Per i test basta creare un'applicazione a vista singola e incollare il codice. Crea per Bottoni- "Start calcolare" e "Stop calcolare" nel file di ViewController.xib, e collegare le loro IBActions con startCalculationPressed e 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 

Aggiungi nuova classe MyModel al progetto:

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 

UPDATE Martin è giusto, performSelectorOnMainThread mantiene sempre sé, quindi non c'è w come fermare l'esecuzione del codice su altri thread (sia in ARC che in non ARC), quindi dealloc non viene chiamato immediatamente al rilascio del modello. Quindi, dovrebbe essere fatto esplicitamente usando la proprietà appropriata (ad esempio delegato) con il controllo condizionale.

+1

perché non limitarsi a tenere un bool controllato nel ciclo di esecuzione "performCalculateAsync' per terminare la funzione quando è impostato da" stopCalculationPressed "(avrà bisogno di una sorta di mutex prob) forse il tuo programma non ha un ciclo o da qualche parte è possibile inserire questo controllo in modo appropriato, ma se lo fa ... – Fonix

+0

@Fonix Vedete è solo un semplice codice demo. Ci sono più cose nelle classi di modelli reali (elaborazione, callback e così via). Avere tale proprietà surrogata mi costringerebbe ad aggiungere controlli condizionali a tutto il codice rendendolo orribile ... – Centurion

+1

Non si dovrebbe usare la gestione della memoria per controllare il comportamento del programma. La gestione della memoria serve per gestire la memoria e non altro. Se è necessario terminare una discussione o qualche altra operazione, è necessario predisporre un segnale esplicito in tal senso indipendente dalla gestione della memoria. Il tuo progetto precedente era rotto. –

risposta

6

Un oggetto viene deallocato se il suo conteggio di rilascio scende a zero o in linguaggio ARC, se l'ultimo riferimento forte a quell'oggetto è scomparso.

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

aggiunge un forte riferimento alla self, il che spiega il motivo per cui l'oggetto non viene deallocato prima che il thread in background è terminato.

Non c'è modo (che io sappia) di fermare automaticamente un thread in background. Lo stesso vale per i blocchi avviati con dispatch_async() o per NSOperation. Una volta avviato, il thread/blocco/operazione deve monitorare alcune proprietà nei punti dove è stato salvato il salvataggio.

Nell'esempio è possibile monitorare self.delegate. Se questo diventa nil, nessuno è più interessato al risultato, , quindi il thread in background può tornare. In tal caso, avrebbe senso dichiarare la proprietà delegate come atomic.

noti che self.delegate anche quest'ultimo viene regolato automaticamente nil se il controller della vista si rilascia (perché è una struttura debole), anche se non è stopCalculationPressed stato chiamato.

+0

Mi interessa sapere come l'esempio dell'OP abbia funzionato con la gestione manuale della memoria. In ARC e nella gestione della memoria manuale 'performSelectorInBackground' esegue il dump del conteggio dei ritardi, quindi in entrambi i casi sembra che' MyModel' sarà deallocato solo una volta terminato 'performCalculateAsync'. – Barjavel

+0

@Barjavel: Sì, è un buon punto. –