Risposta breve
Invece di accedere self
direttamente, dovresti accedervi indirettamente, da un riferimento che non verrà mantenuto. Se non stai usando il conteggio di riferimento automatico (ARC) , puoi farlo:
__block MyDataProcessor *dp = self;
self.progressBlock = ^(CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}
La __block
parola chiave indica le variabili che possono essere modificate all'interno del blocco (non lo stiamo facendo) ma non vengono automaticamente conservate quando il blocco viene mantenuto (a meno che non si stia utilizzando ARC). Se lo fai, devi essere sicuro che nient'altro tenterà di eseguire il blocco dopo il rilascio dell'istanza MyDataProcessor. (Data la struttura del tuo codice, questo non dovrebbe essere un problema.) Altre informazioni__block
.
Se si utilizza ARC , la semantica delle __block
modifiche e il riferimento verranno mantenuti, nel qual caso è necessario dichiararlo__weak
.
Risposta lunga
Supponiamo che tu abbia un codice come questo:
self.progressBlock = ^(CGFloat percentComplete) {
[self.delegate processingWithProgress:percentComplete];
}
Il problema qui è che l'io sta mantenendo un riferimento al blocco; nel frattempo il blocco deve conservare un riferimento a self per recuperare la sua proprietà delegata e inviare un metodo al delegato. Se tutto il resto nella tua app rilascia il suo riferimento a questo oggetto, il suo conteggio di mantenimento non sarà zero (perché il blocco è puntato su di esso) e il blocco non sta facendo nulla di sbagliato (perché l'oggetto sta puntando su di esso) e così la coppia di oggetti colerà nell'heap, occupando memoria ma per sempre irraggiungibile senza un debugger. Tragico, davvero.
Quel caso potrebbe essere facilmente risolto facendo questo invece:
id progressDelegate = self.delegate;
self.progressBlock = ^(CGFloat percentComplete) {
[progressDelegate processingWithProgress:percentComplete];
}
In questo codice, self mantiene il blocco, il blocco mantiene il delegato e non ci sono cicli (visibili da qui; il delegato può conservare il nostro oggetto ma è fuori dalle nostre mani in questo momento). Questo codice non rischia una perdita allo stesso modo, poiché il valore della proprietà del delegato viene acquisito durante la creazione del blocco, anziché essere cercato quando viene eseguito. Un effetto collaterale è che, se si modifica il delegato dopo la creazione di questo blocco, il blocco invierà comunque messaggi di aggiornamento al vecchio delegato. La probabilità che ciò accada o meno dipende dalla tua applicazione.
Anche se sei stato bravo con quel comportamento, non puoi ancora usare quel trucco nel tuo caso:
self.dataProcessor.progress = ^(CGFloat percentComplete) {
[self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};
Qui stai passando self
direttamente al delegato nella chiamata del metodo, quindi devi farlo da qualche parte. Se si ha il controllo sulla definizione del tipo di blocco, la cosa migliore sarebbe passare il delegato nel blocco come parametro:
self.dataProcessor.progress = ^(MyDataProcessor *dp, CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
};
Questa soluzione evita il ciclo di conservazione e chiama sempre il delegato corrente.
Se non riesci a modificare il blocco, puoi gestirlo . Il motivo per cui un ciclo di mantenimento è un avvertimento, non un errore, è che non comportano necessariamente un destino per la tua applicazione. Se MyDataProcessor
è in grado di rilasciare i blocchi al termine dell'operazione, prima che il genitore provi a rilasciarlo, il ciclo verrà interrotto e tutto verrà ripulito correttamente. Se puoi esserne sicuro, la cosa giusta da fare sarebbe usare a #pragma
per sopprimere gli avvisi per quel blocco di codice. (O utilizzare un flag di compilatore per file. Ma non disabilitare l'avviso per l'intero progetto.)
Puoi anche cercare di usare un trucco simile sopra, dichiarando un riferimento debole o non trattenuto e usandolo nel blocco. Per esempio:
__weak MyDataProcessor *dp = self; // OK for iOS 5 only
__unsafe_unretained MyDataProcessor *dp = self; // OK for iOS 4.x and up
__block MyDataProcessor *dp = self; // OK if you aren't using ARC
self.progressBlock = ^(CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}
Tutti e tre i precedenti ti daranno un riferimento senza conservare il risultato, sebbene si comportino tutti in modo leggermente diverso: __weak
proveranno ad azzerare il riferimento quando l'oggetto viene rilasciato; __unsafe_unretained
ti lascerà con un puntatore non valido; __block
aggiungerà effettivamente un altro livello di riferimento indiretto e ti consentirà di modificare il valore del riferimento dall'interno del blocco (irrilevante in questo caso, poiché dp
non viene utilizzato da nessun'altra parte).
La cosa migliore dipenderà dal codice che è possibile modificare e da ciò che non è possibile. Ma spero che questo ti abbia dato alcune idee su come procedere.