Objective-C crash sur le détruire helper bloc
j'ai une application iOS qui s'effondre sur des appels comme __destroy_helper_block_253
et __destroy_helper_block_278
et je ne suis pas vraiment sûr de ce que" destroy_helper_block " est le référencement ou ce que le nombre après qu'il est censé pointer.
quelqu'un avez des conseils pour pouvoir savoir où exactement ces accidents pourraient être présentes?
voici un exemple de traceback (notez que les lignes avec __destroy_helper_block
ne font référence qu'au fichier qu'il contient et rien d'autre, alors que normalement le numéro de ligne serait également inclus).
Thread : Crashed: com.apple.root.default-priority
0 libdispatch.dylib 0x000000018fe0eb2c _dispatch_semaphore_dispose + 60
1 libdispatch.dylib 0x000000018fe0e928 _dispatch_dispose + 56
2 libdispatch.dylib 0x000000018fe0e928 _dispatch_dispose + 56
3 libdispatch.dylib 0x000000018fe0c10c -[OS_dispatch_object _xref_dispose] + 60
4 Example App 0x00000001000fe5a4 __destroy_helper_block_278 (TSExampleApp.m)
5 libsystem_blocks.dylib 0x000000018fe53908 _Block_release + 256
6 Example App 0x00000001000fda18 __destroy_helper_block_253 (TSExampleApp.m)
7 libsystem_blocks.dylib 0x000000018fe53908 _Block_release + 256
8 libdispatch.dylib 0x000000018fe0bfd4 _dispatch_client_callout + 16
9 libdispatch.dylib 0x000000018fe132b8 _dispatch_root_queue_drain + 556
10 libdispatch.dylib 0x000000018fe134fc _dispatch_worker_thread2 + 76
11 libsystem_pthread.dylib 0x000000018ffa16bc _pthread_wqthread + 356
Edit 1: Voici un exemple d'un des blocs définis dans le fichier où le crash se produit (avec le code propre à l'application éliminé).
- (void)doSomethingWithCompletion:(void (^)())completion {
void (^ExampleBlock)(NSString *) = ^{
NSNotification *notification = [NSNotification notificationWithName:kExampleNotificationName object:nil userInfo:nil];
[[NSNotificationCenter defaultCenter] postNotification:notification];
if (completion) {
completion();
}
};
// Async network call that calls ExampleBlock on either success or failure below...
}
il y a beaucoup d'autres blocs dans le fichier, mais la plupart d'entre eux sont fournis comme arguments aux méthodes au lieu d'être définis d'abord et ensuite référencés plus tard.
Edit 2: ajouté plus de contexte à la fonction ci-dessus.
7 réponses
chaque image de la trace de la pile devrait vous donner un indice sur ce que libDispatch fait pour causer le crash. Travailler à notre façon à partir du bas:
11 libsystem_pthread.dylib 0x000000018ffa16bc _pthread_wqthread
10 libdispatch.dylib 0x000000018fe134fc _dispatch_worker_thread2 + 76
ces deux fonctions font tourner un fil ouvrier et le font fonctionner. Dans le processus, il met également en place un pool autorelease pour le fil.
9 libdispatch.dylib 0x000000018fe132b8 _dispatch_root_queue_drain + 556
cette fonction signale le début du processus de destruction de la file d'attente. Le bassin d'autorelase spécifique au fil est drainé, et dans le traiter toutes les variables référencées par cette file d'attente sont libérés. Comme il s'agit de libDispatch, cela signifie que l'objet mach sous-jacent et le bloc de travail que vous avez soumis doivent être supprimés...
7 libsystem_blocks.dylib 0x000000018fe53908 _Block_release + 256
6 Example App 0x00000001000fda18 __destroy_helper_block_253 (TSExampleApp.m)
5 libsystem_blocks.dylib 0x000000018fe53908 _Block_release + 25
4 Example App 0x00000001000fe5a4 __destroy_helper_block_278 (TSExampleApp.m)
qui est précisément ce qui se passe ici. Le numéro 7 est le bloc extérieur et parce qu'il contient un objet non trivial à détruire (encore un autre bloc), le compilateur a généré un destructeur ( __destroy_helper_block_253
) pour se débarrasser de ce bloc intérieur aussi. En appliquant la même logique, nous pouvons en déduire que le bloc intérieur a encore un peu de destruction non triviale à faire.
3 libdispatch.dylib 0x000000018fe0c10c -[OS_dispatch_object _xref_dispose] + 60
2 libdispatch.dylib 0x000000018fe0e928 _dispatch_dispose + 56
1 libdispatch.dylib 0x000000018fe0e928 _dispatch_dispose + 56
Ces lignes sont la cause de tous vos problèmes. Pour une raison quelconque, soit vous avez capturé la file d'attente sur laquelle vous rappelez, soit vous avez capturé un objet qui contient une référence à une file d'attente faiblement telle que quand il va dans le sens du dinosaure, il prend sa file d'attente avec elle. Cela fait en sorte que libDispatch suppose que la file d'attente est terminée et qu'elle continue à désallouer jusqu'à ce que il atteint l'élimination spécifique au sémaphore 1519100920"
0 libdispatch.dylib 0x000000018fe0eb2c _dispatch_semaphore_dispose + 60
sans sémaphore à libérer, mach se plaindra assez pour ne pas retourner KERN_SUCCESS
sur destruction sémaphore, ce qui est une erreur fatale dans libDispatch. En fait, il sera abort()
dans un tel cas-eh bien, techniquement __builtin_trap()
, mais ils accomplissent le même objectif. Parce qu'il n'y a pas de débogueur attaché, descend ton application.
cela soulève donc la question: comment arranger cela? Bien, tout d'abord, vous devez trouver quoi, si quelque chose se réfère à un objet dispatch. Vous avez mentionné que vous faisiez du réseautage asynchrone, donc ce serait l'endroit de vérifier d'abord. Si l'un de ces objets se trouve à contenir une file d'attente ou un sémaphore, ou renvoie un objet qui le fait, et que vous ne le capturez pas fortement dans l'un de ces blocs, c'est précisément ce qui se produit lorsque le bloc passe hors de portée avec l'objet.
Il n'y a pas beaucoup d'aller ici, mais je soupçonne que le bloc n'est jamais déplacé vers le tas. Les blocs sont créés sur la pile par défaut. Le compilateur peut souvent trouver le moment de les déplacer en tas, mais la façon dont vous passez celui-ci d'un bloc à l'autre ne le fait probablement jamais.
j'ajouterais un completionCopy = [completion copy]
pour le forcer sur le tas. Puis travailler avec completionCopy
. Voir réponse de bbum concernant le stockage des blocs dans dictionnaire. Avec ARC, vous n'avez plus besoin d'appeler Block_copy()
et Block_release()
, mais je pense que vous voulez toujours appeler -copy
ici.
hypothèse:
-
doSomethingWithCompletion:
créeExampleBlock.
- vous démarrez une opération de réseau asynchrone.
-
doSomethingWithCompletion:
les retours, et lesExampleBlock
est sorti. - le fonctionnement asynchrone du réseau se termine, et appelle
ExampleBlock
.
dans ce cas, le pointeur vers le bloc serait déréférencé après qu'il ait été désalloué. (Peut-être est-ce intermittent si l'on se base sur le fait que le bassin d'autorelease s'est drainé ou si d'autres zones de mémoire à proximité ont été libérées.)
3 solutions possibles:
1. Stocker le bloc dans une propriété
stocker le bloc dans une propriété:
@property (nonatomic, copy) returnType (^exampleBlock)(parameterTypes);
puis en code,
self.exampleBlock = …
Un problème avec cette approche est que vous ne pouvez jamais avoir un exampleBlock
.
2. Stocker le bloc dans un tableau
Pour contourner ce problème, vous pouvez stocker les blocs dans une collection (comme NSMutableArray
):
@property (nonatomic, strong) NSMutableArray *blockArray;
puis en code:
self.blockArray = [NSMutableArray array];
// Later on…
[self.blockArray addObject:exampleBlock];
vous pouvez enlever le bloc du tableau quand il est correct de le désallouer.
3. Travailler autour du problème de stockage en passant simplement le bloc autour de
au lieu de gérer le stockage et la destruction de vos blocs, remaniez votre code de façon à ce que exampleBlock
soit transmis entre les différentes méthodes jusqu'à la fin de votre opération.
alternativement, vous pouvez utiliser NSBlockOperation
pour le code asynchrone, et définir son completionBlock
pour la réponse-code fini, et l'ajouter à une Nsopérationqueue.
je pense que le completion est libéré dans votre appel async qui pourrait être à l'origine du crash.
je soupçonne, le problème n'est pas dans votre code mais ailleurs.
un problème possible est celui-ci:
IFF il y a des objets UIKit qui sont capturés dans le bloc completion
vous avez probablement un bug subtil quand le bloc est exécuté sur un thread non-principal et ce bloc garde le dernier forte référence à ces objets UIKit:
quand le bloc completion
se termine, il est Bloc obtenir désalloué et avec cela, toutes les variables importées sont " détruites "ce qui signifie qu'en cas de pointeurs readable, elles reçoivent un message release
. Si c'était la dernière référence forte, l'objet capturé est désallocalisé ce qui se produit dans un thread non-principal - et cela peut être fatal pour les objets UIKit.
Je ne vois rien de mal avec le code posté et je pense que le bug est ailleurs.
aussi blocs de nidification semble inutile et complique la gestion de la mémoire et rend probablement plus difficile de trouver la cause de l'accident.
pourquoi ne pas commencer par déplacer le code de votre ExampleBlock
directement dans le bloc completion
?
qu'en est-il de cette solution: si vous appellerez plus tard un bloc qui n'est pas dans la portée courante, alors vous devriez appeler une copie dessus pour déplacer ce bloc dans le tas à partir de la pile
- (void)doSomethingWithCompletion:(void (^)())completion {
void (^ExampleBlock)(NSString *) = [^{
NSNotification *notification = [NSNotification notificationWithName:kExampleNotificationName object:nil userInfo:nil];
[[NSNotificationCenter defaultCenter] postNotification:notification];
if (completion) {
completion();
}
} copy];
// Async network call that calls ExampleBlock on either success or failure below...
}