Quando si riceve un blocco come parametro del metodo, quel blocco potrebbe essere l'originale che era stato creato nello stack o che poteva essere copiato (un blocco sull'heap). Per quanto ne so, non c'è modo di dirlo. Quindi la regola generale è che se si esegue il blocco all'interno del metodo che lo sta ricevendo, non è necessario copiarlo. Se si intende passare quel blocco a un altro metodo (che può essere o non essere eseguito immediatamente), non è necessario copiarlo (il metodo di ricezione dovrebbe copiarlo se intende mantenerlo in giro). Tuttavia, se si intende archiviare il blocco in qualsiasi modo per un posto per l'esecuzione successiva, è necessario copiarlo.L'esempio principale molte persone utilizzano sia una sorta di blocco completamento detenuti come una variabile di istanza:
typedef void (^IDBlock) (id);
@implementation MyClass{
IDBlock _completionBlock;
}
Tuttavia, è anche necessario copiare se avete intenzione di aggiungerlo a qualsiasi tipo di classe di raccolta, come un NSArray o NSDictionary. In caso contrario, riceverai degli errori (molto probabilmente EXC_BAD_ACCESS) o forse un danneggiamento dei dati quando proverai a eseguire il blocco in un secondo momento.
Quando si esegue un blocco, è importante verificare prima se il blocco è nil
. Objective-c consente di passare nil
a un parametro del metodo di blocco. Se quel blocco è nullo, otterrai EXC_BAD_ACCESS quando provi ad eseguirlo. Fortunatamente questo è facile da fare. Nel tuo esempio, scrivi:
- (void)testWithBlock:(void (^)(NSString *))block {
NSString *testString = @"Test";
if (block) block(testString);
}
Ci sono considerazioni sulle prestazioni nella copia dei blocchi. Rispetto alla creazione di un blocco sullo stack, copiare un blocco sull'heap non è banale. Non è un grosso problema in generale, ma se si utilizza un blocco in modo iterativo o si utilizza un gruppo di blocchi in modo iterativo e si copiano su ogni esecuzione, si creerà un impatto sulle prestazioni. Quindi, se il tuo metodo - (void)testWithBlock:(void (^)(NSString *))block;
era in un qualche tipo di ciclo, la copia di quel blocco potrebbe danneggiare le tue prestazioni se non hai bisogno di copiarlo.
Un altro posto in cui è necessario copiare un blocco è se si intende richiamare quel blocco di per sé (ricorsione del blocco). Questo non è tutto ciò che è comune, ma è necessario copiare il blocco se si intende farlo. Vedere la mia domanda/risposta su SO qui: Recursive Blocks In Objective-C.
Infine, se si intende memorizzare un blocco, è necessario prestare molta attenzione alla creazione di cicli di conservazione. I blocchi manterranno qualsiasi oggetto passato in esso, e se tale oggetto è una variabile di istanza, manterrà la classe della variabile di istanza (self). Personalmente amo i blocchi e li uso sempre. Ma c'è un motivo per cui Apple non usa/memorizza blocchi per le loro classi UIKit e invece si attacca a un modello di destinazione/azione o delegato. Se tu (la classe che crea il blocco) stai mantenendo la classe che sta ricevendo/copiando/memorizzando il blocco, e in quel blocco fai riferimento a te stesso oa QUALSIASI variabile di istanza di classe, hai creato un ciclo di conservazione (classA -> classB - > blocco -> classeA). Questo è straordinariamente facile da fare, ed è qualcosa che ho fatto troppe volte. Inoltre, "Leaks" in Instruments non lo cattura. Il metodo per aggirare questo problema è semplice: è sufficiente creare una variabile temporanea __weak
variabile (per ARC) o __block
(non ARC) e il blocco non conserverà tale variabile. Così, per esempio, il seguente sarebbe un conservano ciclo se le copie 'oggetto'/memorizza il blocco:
[object testWithBlock:^(NSString *test){
_iVar = test;
NSLog(@"[%@]", test);
}];
Tuttavia, per risolvere questo (utilizzando ARC):
__weak IVarClass *iVar = _iVar;
[object testWithBlock:^(NSString *test){
iVar = test;
NSLog(@"[%@]", test);
}];
È inoltre possibile fare qualcosa di simile:
__weak ClassOfSelf _self = self;
[object testWithBlock:^(NSString *test){
_self->_iVar = test;
NSLog(@"[%@]", test);
}];
Nota che molte persone non piace quanto sopra perché ritengono che sia fragile, ma è un modo valido di accesso variabili. Aggiornamento - Il compilatore corrente ora avverte se si tenta di accedere direttamente a una variabile usando '->'. Per questo motivo (oltre a motivi di sicurezza) è meglio creare una proprietà per le variabili a cui si desidera accedere. Quindi invece di _self->_iVar = test;
dovresti usare: _self.iVar = test;
.
UPDATE (+ info)
In generale, è meglio prendere in considerazione il metodo che riceve il blocco, come responsabile per determinare se il blocco deve essere copiato piuttosto che il chiamante. Questo perché il metodo di ricezione può essere l'unico in grado di sapere per quanto tempo il blocco deve essere mantenuto attivo o se deve essere copiato. Tu (come il programmatore) ovviamente saprai questa informazione quando scrivi la chiamata, ma se consideri mentalmente il chiamante e il ricevente in oggetti separati, il chiamante dà al ricevitore il blocco e lo ha fatto. Pertanto, non dovrebbe essere necessario sapere cosa viene fatto con il blocco dopo che è sparito. D'altra parte, è possibile che il chiamante abbia già copiato il blocco (forse ha memorizzato il blocco e lo sta ora consegnando ad un altro metodo) ma il ricevitore (che intende anche archiviare il blocco) dovrebbe comunque copiare il blocco (anche se il blocco è già stato copiato). Il ricevitore non può sapere che il blocco è già stato copiato e alcuni blocchi che riceve possono essere copiati mentre altri potrebbero non esserlo. Quindi il ricevitore dovrebbe sempre copiare un blocco che intende mantenere in giro? Ha senso? Questo in sostanza è una buona pratica di progettazione orientata agli oggetti. Fondamentalmente, chi ha le informazioni è responsabile per gestirle.
I blocchi sono ampiamente utilizzati nel GCD di Apple (Grand Central Dispatch) per abilitare facilmente il multi-threading. In generale, non è necessario copiare un blocco quando lo si invia su GCD. Stranamente, questo è leggermente contro-intuitivo (se ci pensate) perché se si invia un blocco in modo asincrono, spesso il metodo in cui il blocco è stato creato ritornerà prima dell'esecuzione del blocco, che in genere significherebbe che il blocco scadrà poiché è un oggetto stack. Non penso che GCD copi il blocco nello stack (l'ho letto da qualche parte ma non sono riuscito a trovarlo di nuovo), invece penso che la vita del thread venga estesa inserendo un altro thread.
Mike Ash dispone di ampi articoli su blocchi, GCD e ARC, che si possono trovare utili:
Stai usando ARC? –
@pcperini, si. – rid