14

Sto cercando di mantenere un riferimento a un blocco che è stato passato alla mia classe con un metodo, per chiamare in un secondo momento. Sto avendo problemi, tuttavia, mantenendo un riferimento ad esso.Il blocco viene rilasciato mentre in NSDictionary (ARC)

Il modo ovvio, ho pensato, era di aggiungerlo a una raccolta di ivar, che dovrebbero mantenere forti riferimenti al loro contenuto. Ma quando provo a tirarlo indietro, è zero.

Il codice è piuttosto semplice:

typedef void (^DataControllerCallback)(id rslt); 

@interface DataController : NSObject { 
    NSMutableArray* queue; 
} 
- (void) addBlock:(DataControllerCallback)callback; 
- (void) functionToBeCalledLater; 
@end 

@implementation DataController 

- (id) init { 
    self = [super init]; 
    if (self != nil) {   
     queue = [NSMutableArray new]; 
    } 
    return self; 
} 

- (void) addBlock:(DataControllerCallback)callback { 
    NSDictionary* toAdd = [NSDictionary dictionaryWithObjectsAndKeys: 
     [callback copy], @"callback", 
     @"some other data", @"data", nil]; 
    [queue addObject:toAdd]; 
} 

- (void) functionToBeCalledLater { 
    NSDictionary* dict = [queue lastObject]; 
    NSLog(@"%@", [dict objectForKey:@"data"]; //works 
    DataControllerCallback callback = [dict objectForKey:@"callback"]; //this is nil 
    callback(@"an arguemnt"); //EXC_BAD_ACCESS 
} 

Cosa sta succedendo?


Aggiornamento: Ho provato con [callback copy] e proprio callback inserendo nel dizionario, né opere.


Update 2: Se ho appena bastone il mio blocco in un NSMutableSet, a patto che io chiamo copy, sto bene. Funziona alla grande. Ma se è in un NSDictionary, non è così.

L'ho effettivamente testato mettendo un breakpoint subito dopo che NSDict è stato creato e il callback non viene mai inserito. La descrizione riporta chiaramente "1 coppia chiave-valore", non due.

Attualmente mi sto aggirando con una classe specializzata che funge solo da contenitore. La proprietà callback viene dichiarata come strong; Non ho nemmeno bisogno di usare copy.

La domanda continua, tuttavia: perché sta succedendo? Perché un archivio NSDictionary non è un blocco? Ha qualcosa a che fare con il fatto che sto prendendo di mira iOS 4.3 e quindi ARC deve essere incorporato come libreria statica?


Aggiornamento 3: Signore e signori: sono un idiota.

Il codice che ho presentato qui era ovviamente una versione semplificata del codice attuale; in particolare, stava lasciando alcune coppie chiave/valore fuori dal dizionario.

Se sei memorizzare un valorein un NSDictionary utilizzando [NSDictionary dictionaryWithObjectsAndKeys:], è meglio essere dannatamente sicuro uno di questi valori non è nil.

Uno di questi era.

ICYMI, causava una chiusura anticipata dell'elenco argomenti. Avevo un argomento user-type passato in uno dei metodi "aggiungi alla coda" e potevi, ovviamente, passare "nil". Poi, quando ho costruito il dizionario, il chucking in quell'argomento ha indotto il costruttore a pensare che avevo terminato la lista degli argomenti. @"callback" era l'ultimo valore nel costruttore dizionario e non veniva mai memorizzato.

+0

Ho copiato e incollato questo codice con [callback copy] e tutto ha funzionato correttamente. –

+0

Il 'functionToBeCalledLater' si verifica in una diversa iterazione del ciclo di esecuzione, anche se non pensavo che fosse importante, ma forse ha qualcosa a che fare con la posizione di conservazione e rilascio di ARC. –

+0

Ho chiamato functionToBeCalledLater su una diversa iterazione del runloop anche (fatto un piccolo pulsante per questo, e lo ho colpito più volte in effetti) –

risposta

31

Contrariamente all'approccio errato diffuso, ARC non disimpegna automaticamente i blocchi passati come argomenti ai metodi. Disimpegna automaticamente solo quando viene restituito un blocco da un metodo/funzione.

I.e. Questo....

[dict setObject: ^{;} forKey: @"boom"]; 

... andrà in crash se dict sopravvive al di là del campo di applicazione e si tenta di utilizzare il blocco (in realtà, non sarà in questo caso, perché questo è un blocco statico, ma questo è un dettaglio compilatore che si non posso fare affidamento su).

Questo è documented here:

Come blocchi lavorano in ARC?

I blocchi "funzionano" quando si passano blocchi in pila in modalità ARC, ad esempio come in un ritorno. Non è necessario chiamare Block Copy più. L'utente ha ancora bisogno di usare [^ {} copia] quando passa "in basso" allo stack in arrayWithObjects: e in altri metodi che mantengono.

Il comportamento valore restituito potrebbe essere automatizzato perché è sempre corretta per restituire un blocco basato mucchio (e sempre un errore per restituire un blocco basato stack). Nel caso di un blocco come argomento, è impossibile automatizzare il comportamento in un modo che sarebbe sia molto efficiente che sempre corretto.

Probabilmente l'analizzatore avrebbe dovuto avvisare di questo utilizzo. Se così non fosse, presenta un bug.

(I derped un pila quando volevo dire un mucchio Mi dispiace..)


Il compilatore non automatizzare blocca-come-parametri per alcuni motivi:

  • copiando inutilmente un blocco per l'heap può essere una penalità significativa delle prestazioni
  • copie multiple di un blocco possono moltiplicare quella prestazione penalizzante significativamente.

cioè .:

doSomethingSynchronous(aBlock); 
doSomethingSynchronous(aBlock); 
doSomethingSynchronous(aBlock); 
doSomethingSynchronous(aBlock); 

Se ciò dovesse implicare quattro operazioni Block_copy() e ablock conteneva una notevole quantità di stato catturato, sarebbe un colpo enorme potenziale.

• Ci sono solo tante ore al giorno e l'automazione della gestione dei parametri è piena di casi limite non ovvi. Se questo fosse gestito automaticamente in futuro, si potrebbe fare senza rompere il codice esistente e, quindi, forse lo si farà in futuro.

I.e. il compilatore potrebbe generare:

aBlock = [aBlock copy]; 
doSomethingSynchronous(aBlock); 
doSomethingSynchronous(aBlock); 
doSomethingSynchronous(aBlock); 
doSomethingSynchronous(aBlock); 
[aBlock release]; 

Non solo questo risolvere il problema di un blocco-come-param, ma sarebbe anche produrre una sola copia del blocco attraverso tutti i possibili utilizzi.


La domanda si ferma, però: perché sta succedendo questo?Perché un NSDictionary non memorizza un blocco? Ha qualcosa a che fare con il fatto che sto targeting per iOS 4.3 e quindi ARC deve essere integrato come una libreria statica ?

Sta succedendo qualcosa di strano. Per coincidenza, ho usato i blocchi come valori in un'applicazione basata su ARC nell'ultima settimana e sta funzionando bene.

Hai un esempio minimo utile?

+0

Bene, la tua risposta è chiaramente migliore della mia era =) +1 – justin

+1

È sempre corretto restituire un blocco basato sullo stack, ed è sempre un errore restituire un blocco basato sullo stack? Sospetto che uno di quelli dovrebbe dire "mucchio". –

+0

In 'è sempre corretto restituire un blocco basato su stack (e sempre un errore per restituire un blocco basato su stack)', non dovrebbe essere il blocco _static_ nel primo caso? –

Problemi correlati