Bloc est libéré pendant que dans NSDictionary (ARC)

J'essaie de conserver une reference à un bloc qui a été transmis à ma class par une méthode, pour appeler plus tard. J'ai des problèmes, cependant, en maintenant une reference à cela.

La manière évidente, je pensais, était de l'append à une collection d'ivar, qui sont censés maintenir de fortes references à leur contenu. Mais quand j'essaie de le retirer, c'est nul.

Le code est assez 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 ce passe-t-il?


Mise à jour: je l'ai essayé avec [callback copy] et juste l'insertion de callback dans le dictionary, ni fonctionne.


Mise à jour 2: Si je colle simplement mon bloc dans un NSMutableSet, tant que j'appelle la copy , je vais bien. Cela fonctionne très bien. Mais si c'est dans un NSDictionary, ce n'est pas le cas.

En fait, je l'ai testé en mettant un point d'arrêt juste après la création du NSDict et le callback n'est jamais inséré. La description lit clairement "1 paire key-valeur", pas deux.

Je suis en train de contourner cela avec une class spécialisée qui agit comme un conteneur. La propriété de callback est déclarée comme strong ; Je n'ai même pas besoin d'utiliser la copy .

La question subsiste cependant: pourquoi cela se produit-il? Pourquoi un NSDictionary ne stocke-t-il pas un bloc? Cela a-t-il quelque chose à voir avec le fait que je cible iOS 4.3 et que l'ARC doit donc être embedded en tant que bibliothèque statique?


Mise à jour 3: Mesdames et messieurs: Je suis un idiot.

Le code que j'ai présenté ici était évidemment une version simplifiée du code réel; Plus particulièrement, il laissait certaines paires key / valeur hors du dictionary.

Si vous stockez une valeur dans un NSDictionary en utilisant [NSDictionary dictionaryWithObjectsAndKeys:] , vous feriez mieux de vous assurer que l'une de ces valeurs n'est pas nil .

L'un d'eux était.

ICYMI, il provoquait une fin anticipée de la list des arguments. Un argument de type userInfo était passé dans l'une des methods "add to queue", et vous pouviez, bien sûr, passer "nil". Puis, quand j'ai construit le dictionary, le fait de taper dans cet argument a amené le constructor à penser que j'avais terminé la list des arguments. @"callback" était la dernière valeur dans le constructor du dictionary et il n'a jamais été stocké.

Contrairement à la mauvaise design répandue, l'ARC ne dé-emstack pas automatiquement les Blocs passés en arguments aux methods . Il ne se dé-emstack automatiquement que lorsqu'un bloc est renvoyé par une méthode / fonction.

C'est à dire ….

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

… va planter si dict survit au-delà de la scope et que vous essayez d'utiliser le bloc (en fait, ce ne sera pas dans ce cas car c'est un bloc statique, mais c'est un détail du compilateur sur lequel vous ne pouvez pas countr).

Ceci est documenté ici :

Comment fonctionnent les blocs dans ARC?

Les blocs "fonctionnent" lorsque vous passez des blocs dans la stack en mode ARC, comme dans un return. Vous n'avez plus besoin d'appeler Block Copy. Vous devez toujours utiliser [^ {} copy] lorsque vous arrayWithObjects: la stack dans arrayWithObjects: et d'autres methods qui la retiennent.

Le comportement de la valeur de return peut être automatisé car il est toujours correct de renvoyer un bloc basé sur le tas (et toujours une erreur pour renvoyer un bloc basé sur la stack). Dans le cas d'un bloc-comme-un argument, il est impossible d'automatiser le comportement d'une manière qui serait à la fois très efficace et toujours correcte.

L'parsingur aurait probablement dû avertir de cette utilisation. Si ce n'est pas le cas, envoyez un bogue.

(J'ai fait une stack quand je voulais dire un tas .) Désolé pour ça.


Le compilateur n'automatise pas les blocs en tant que parameters pour plusieurs raisons:

  • copyr inutilement un bloc sur le tas peut être une pénalité de performance significative
  • les copys multiples d'un bloc peuvent multiplier cette pénalité de performance de manière significative.

C'est à dire:

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

Si cela impliquait quatre opérations Block_copy () et que aBlock contenait une quantité significative d'état capturé, ce serait un énorme succès potentiel.

• Il n'y a que très peu d'heures dans la journée et l'automation du traitement des parameters est fréquente avec des cas de bords non évidents. Si cela était géré automatiquement dans le futur, cela pourrait être fait sans casser le code existant et, ainsi, peut-être que cela sera fait dans le futur.

C'est à dire que le compilateur pourrait générer:

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

Non seulement cela réglerait le problème d'un bloc-comme-param, mais cela produirait aussi seulement une copy du bloc pour toutes les utilisations potentielles.

La question subsiste cependant: pourquoi cela se produit-il? Pourquoi un NSDictionary ne stocke-t-il pas un bloc? Cela a-t-il quelque chose à voir avec le fait que je cible iOS 4.3 et que l'ARC doit donc être embedded en tant que bibliothèque statique?

Quelque chose de bizarre se passe alors. Par coïncidence, j'ai utilisé des blocs-comme-valeurs dans une application basée sur ARC la semaine dernière et ça fonctionne bien.

Avez-vous un exemple minimal pratique?