11

Ho difficoltà a convertire alcuni codici NSOperation in ARC. L'oggetto operazione utilizza un blocco di completamento, che a sua volta contiene un blocco GCD che aggiorna l'interfaccia utente sul thread principale. Poiché faccio riferimento al mio oggetto operazione dall'interno del suo blocco di completamento, sto utilizzando un puntatore __weak per evitare perdite di memoria. Tuttavia, il puntatore è già impostato su zero al momento dell'esecuzione del codice.Riferimento a un oggetto NSOperation nel proprio blocco di completamento con ARC

L'ho ristretto a questo esempio di codice. Qualcuno sa dove ho sbagliato e il modo giusto per farlo?

NSOperationSubclass *operation = [[NSOperationSubclass alloc] init]; 
__weak NSOperationSubclass *weakOperation = operation; 

[operation setCompletionBlock:^{ 
    dispatch_async(dispatch_get_main_queue(), ^{ 

     // fails the check 
     NSAssert(weakOperation != nil, @"pointer is nil"); 

     ... 
    }); 
}]; 
+1

Bene, ciò che è andato storto è che un puntatore debole non è proprietario. Se non c'è nient'altro che tiene la variabile (e non c'è), verrà eliminata. Sei sicuro di avere una perdita se usi 'operazione'? Sembra che dovrebbe sparire quando viene rilasciato il blocco di completamento, che dovrebbe essere appena chiamato. (Potrebbe essere ingenuo, però.) –

+0

ARC si stava lamentando al momento della compilazione. Senza di esso stavo usando direttamente il puntatore dell'operazione (e non credo che stavo perdendo memoria). –

+1

Buona fortuna con questo. Penso di aver lottato contro di esso per diverse ore prima di arrendermi e fare qualcos'altro. Ma è passato un po 'di tempo. :) –

risposta

10

Io non sono certo di questo, ma il modo corretto per farlo è forse per aggiungere __block alla variabile in questione, e quindi impostarlo a zero alla fine del blocco per assicurare che sia rilasciato. See this question.

Il nuovo codice sarebbe simile a questa:

NSOperationSubclass *operation = [[NSOperationSubclass alloc] init]; 
__block NSOperationSubclass *weakOperation = operation; 

[operation setCompletionBlock:^{ 
    dispatch_async(dispatch_get_main_queue(), ^{ 

     // fails the check 
     NSAssert(weakOperation != nil, @"pointer is nil"); 

     ... 
     weakOperation = nil; 
    }); 

}]; 
+3

Hai ragione, credo. Grazie! –

14

Un'altra opzione sarebbe quella:

NSOperationSubclass *operation = [[NSOperationSubclass alloc] init]; 
__weak NSOperationSubclass *weakOperation = operation; 

[operation setCompletionBlock:^{ 
    NSOperationSubclass *strongOperation = weakOperation; 

    dispatch_async(dispatch_get_main_queue(), ^{ 
     assert(strongOperation != nil); 
     ... 
    }); 
}]; 

[operationQueue addOperation:operation]; 

Presumo che anche aggiungere oggetto operazione per un NSOperationQueue. In tal caso, la coda sta mantenendo un'operazione. Probabilmente lo sta mantenendo anche durante l'esecuzione del blocco di completamento (anche se non ho trovato conferma ufficiale sul blocco di completamento).

Ma all'interno del blocco di completamento viene creato un altro blocco. Quel blocco verrà eseguito in un momento successivo, probabilmente dopo il completamento del blocco di completamento di NSOperations. Quando ciò accade, operation verrà rilasciato dalla coda e weakOperation sarà nil. Ma se creiamo un altro riferimento forte allo stesso oggetto dal blocco di completamento dell'operazione, ci assicureremo che operation esista quando viene eseguito il secondo blocco ed evitiamo il ciclo di conservazione perché non catturiamo la variabile operation dal blocco.

Apple fornisce questo esempio in Transitioning to ARC Release Notes vedere l'ultimo snippet di codice in Utilizzare i durevoli qualificatori per evitare cicli di riferimento intensi sezione.

+4

+1 Questa è la risposta corretta.Il 'NSOperation' mantiene il blocco di completamento, quindi è sicuro usare un riferimento debole ad esso nel blocco di completamento, perché è garantito che sia ancora vivo. Tuttavia, il problema dell'OP è che lo stanno usando nel secondo blocco, che viene eseguito in seguito, e il riferimento debole non è garantito per essere vivo lì. La soluzione corretta è che il secondo blocco abbia un forte riferimento a 'NSOperation' – user102008

4

La risposta accettata è corretta. Tuttavia non v'è alcuna necessità di weakify operazione come di iOS 8/Mac OS 10.10:

la citazione da NSOperation documentation on @completionBlock:

In iOS 8 e versioni successive e OS X v10.10 e più tardi, questa proprietà è impostata a zero dopo che il blocco di completamento inizia l'esecuzione.

Vedere anche this tweet da Pete Steinberger.