El bloque se libera mientras que en NSDictionary (ARC)

Estoy tratando de retener una reference a un Bloque que se ha pasado a mi class por un método, para llamar en otro momento. Sin embargo, estoy teniendo problemas para mantener una reference a este.

La forma más obvia, pensé, era agregarla a una colección ivar, todas las cuales se supone que mantienen fuertes references a su contenido. Pero cuando trato de recuperarla, es nula.

El código es bastante simple:

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 } 

¿Que está pasando?


Actualización: Lo he intentado con [callback copy] y simplemente insert la callback en el dictionary, tampoco funciona.


Actualización 2: si solo pego mi bloque en un NSMutableSet, siempre que llame a copy , estoy bien. Funciona muy bien Pero si está en un dictionary NS, no es así.

De hecho, he probado poniendo un punto de interrupción justo después de que se crea el NSDict y la callback nunca se inserta. La descripción lee claramente "1 par key-valor", no dos.

Actualmente estoy dando vueltas a esto con una class especializada que solo actúa como un contenedor. La propiedad de callback se declara como strong ; Ni siquiera necesito usar copy .

Sin embargo, la pregunta sigue: ¿por qué sucede esto? ¿Por qué un NSDictionary no almacena un Bloque? ¿Tiene algo que ver con el hecho de que estoy apuntando a iOS 4.3 y, por lo tanto, ARC debe estar integrado como una biblioteca estática?


Actualización 3: Damas y caballeros: soy un idiota.

El código que presenté aquí era obviamente una versión simplificada del código real; más particularmente, estaba dejando algunas parejas key / valor fuera del dictionary.

Si está almacenando un valor en un NSDictionary usando [NSDictionary dictionaryWithObjectsAndKeys:] , será mejor que esté completamente seguro de que uno de esos valores no es nil .

Uno de ellos era

ICYMI, estaba causando una terminación anticipada de la list de arguments. Tenía un argumento de tipo userInfo pasado a uno de los methods de "agregar a la queue" y, por supuesto, podría pasar "nil". Luego, cuando construí el dictionary, tirar el argumento causó que el constructor pensara que había terminado la list de arguments. @"callback" fue el último valor en el constructor del dictionary y nunca se almacenó.

Contrariamente a la mala concepción popular, ARC no astack automáticamente los bloques pasados ​​como arguments a methods . Solo se de-astack automáticamente cuando se devuelve un bloque de un método / function.

Es decir esto …

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

… se bloqueará si la dict sobrevive más allá del scope e intenta utilizar el bloque (en realidad, no lo hará en este caso porque es un bloque estático, pero es un detalle del comstackdor en el que no puede confiar).

Esto está documentado aquí :

¿Cómo funcionan los bloques en ARC?

Los bloques "solo funcionan" cuando pasas bloques por la stack en modo ARC, como en un retorno. Ya no tiene que llamar a Block Copy. Todavía necesita usar [^ {} copy] al pasar "hacia abajo" la stack en arrayWithObjects: y otros methods que hacen una retención.

El comportamiento del valor de retorno podría automatizarse porque siempre es correcto devolver un bloque basado en montón (y siempre un error para devolver un bloque basado en una stack). En el caso de un bloque como argumento, es imposible automatizar el comportamiento de una manera que sea a la vez muy eficiente y siempre correcta.

Probablemente el analizador debería haber advertido sobre este uso. Si no lo hizo, presente un error.

(Derome un montón cuando me refiero a un montón . Perdón por eso.)


El comstackdor no automatiza los bloques como parameters por algunas razones:

  • copyr innecesariamente un bloque en el montón puede ser una pena de performance significativa
  • las copys múltiples de un bloque pueden multiplicar significativamente la penalización de performance.

Es decir:

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

Si eso implicara cuatro operaciones Block_copy () y aBlock contenía una cantidad significativa de estado capturado, eso sería un gran golpe potencial.

• Hay tan pocas horas en el día y la automation del event handling los parameters está llena de casos de borde no obvios. Si esto se manejara automáticamente en el futuro, podría hacerse sin romper el código existente y, por lo tanto, tal vez se haga en el futuro.

Es decir, el comstackdor podría generar:

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

Esto no solo solucionaría el problema de block-as-param, sino que también solo produciría una copy del bloque en todos los usos potenciales.

Sin embargo, la pregunta sigue: ¿por qué sucede esto? ¿Por qué un NSDictionary no almacena un Bloque? ¿Tiene algo que ver con el hecho de que estoy apuntando a iOS 4.3 y, por lo tanto, ARC debe estar integrado como una biblioteca estática?

Algo extraño está sucediendo, entonces. Coincidentemente, he estado usando blocks-as-values ​​en una aplicación basada en ARC en la última semana y está funcionando bien.

¿Tienes a mano un ejemplo mínimo?