Objective-C crash on __destroy_helper_block_

Tengo una aplicación de iOS que se __destroy_helper_block_253 en llamadas como __destroy_helper_block_253 y __destroy_helper_block_278 y no estoy realmente seguro de qué es lo que "destroy_helper_block" está haciendo reference o a qué número se debe apuntar después.

¿Alguien tiene algún consejo sobre cómo localizar exactamente dónde podrían estar ocurriendo estos crashs?

Aquí hay un ejemplo de rastreo (obsérvese que las líneas con __destroy_helper_block solo hacen reference al file en el que está contenido y nada más, cuando normalmente también se includeá el número de línea).

 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 

Editar 1: Aquí hay un ejemplo de uno de los bloques definidos en el file donde se produce el locking (con el código específico de la aplicación editado).

 - (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... } 

Hay muchos otros bloques en el file, pero la mayoría de ellos se proporcionan como arguments a los methods en lugar de ser definidos primero y luego referencedos más adelante.

Edición 2: se agregó más context a la function anterior.

Cada fotogtwig de la traza de la stack debe darle una pista de lo que está haciendo libDispatch para provocar el locking. Trabajando desde la parte inferior:

 11 libsystem_pthread.dylib 0x000000018ffa16bc _pthread_wqthread 10 libdispatch.dylib 0x000000018fe134fc _dispatch_worker_thread2 + 76 

Estas dos funciones giran un hilo de trabajo y lo ejecutan. En el process, también configura un grupo de autorelease para el hilo.

 9 libdispatch.dylib 0x000000018fe132b8 _dispatch_root_queue_drain + 556 

Esta function indica el inicio del process de destrucción de la queue. El grupo de autorelease específico de subprocesss se vacía, y en el process se liberan todas las variables a las que hace reference esa queue en particular. Debido a que se trata de libDispatch, eso significa que el object mach subyacente y el bloque de trabajo que envió tienen que ir …

 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) 

que es precisamente lo que sucede aquí. El número 7 es el bloque exterior y porque contiene un object no trivial para destruir (otro bloque más), el comstackdor generó un destructor ( __destroy_helper_block_253 ) para deshacerse de ese bloque interno también. Aplicando la misma línea de lógica, podemos deducir que el bloque interno tiene aún otro poco de destrucción no trivial para hacer.

 3 libdispatch.dylib 0x000000018fe0c10c -[OS_dispatch_object _xref_dispose] + 60 2 libdispatch.dylib 0x000000018fe0e928 _dispatch_dispose + 56 1 libdispatch.dylib 0x000000018fe0e928 _dispatch_dispose + 56 

Estas líneas son la raíz de todos sus problemas. Por alguna razón, ha capturado la queue a la que está volviendo a llamar o ha capturado un object que tiene una reference a una queue de manera débil, de modo que cuando el dinosaurio sigue el path, toma su queue con él . Esto hace que libDispatch asum que la queue está terminada y continúa en desorder hasta que alcanza la disposition específica del semáforo

 0 libdispatch.dylib 0x000000018fe0eb2c _dispatch_semaphore_dispose + 60 

Sin un semáforo para liberar, mach se quejará lo suficiente como para no devolver KERN_SUCCESS en la destrucción del semáforo, que es un error fatal en libDispatch. De hecho, abort() en un caso así, bien, técnicamente __builtin_trap() , pero __builtin_trap() el mismo objective. Porque no hay depurador adjunto, baja tu aplicación.

Entonces esto plantea la pregunta: ¿cómo arreglas esto? Bueno, primero debe encontrar qué, si hace reference a un object de envío. Mencionaste que estabas haciendo una networking asíncrona, así que ese sería el lugar para verificar primero. Si alguno de esos objects contiene una queue o un semáforo, o hace reference a un object que lo hace, y no lo está capturando con fuerza en ninguno de esos bloques, esto es precisamente lo que sucede cuando el bloque pasa fuera del scope junto con el object .

No hay mucho por hacer aquí, pero sospecho que el bloque nunca se mueve al montón. Los bloques se crean en la stack de forma pnetworkingeterminada. El comstackdor a menudo puede averiguar cuándo moverlos al montón, pero la forma en que está entregando este de bloque a bloque probablemente nunca lo haga.

Yo agregaría una completionCopy = [completion copy] para forzarla en el montón. Luego, trabaja con completionCopy . Vea la respuesta de Bbum sobre el almacenamiento de bloques en dictionarys. Con ARC, ya no necesita llamar a Block_copy() y Block_release() , pero creo que aún desea llamar -copy aquí.

Hipótesis:

  1. doSomethingWithCompletion: crea ExampleBlock.
  2. Comienzas una operación de networking asíncrona.
  3. doSomethingWithCompletion: devuelve, y se libera ExampleBlock .
  4. La operación de networking asíncrona finaliza y llama a ExampleBlock .

En este caso, el puntero al bloque quedaría desreferido después de haber sido desasignado. (Quizás esto sea intermitente en function de si el grupo de autorelease se ha agotado o si se han liberado otras áreas cercanas de memory).

3 posibles soluciones:

1. Almacenar el bloque en una propiedad

Almacenar el bloque en una propiedad:

 @property (nonatomic, copy) returnType (^exampleBlock)(parameterTypes); 

Luego en el código,

 self.exampleBlock = … 

Un problema con este enfoque es que solo puede tener un exampleBlock .

2. Almacena el bloque en una matriz

Para solucionar este problema, puede almacenar los bloques en una colección (como NSMutableArray ):

 @property (nonatomic, strong) NSMutableArray *blockArray; 

luego en el código:

 self.blockArray = [NSMutableArray array]; // Later on… [self.blockArray addObject:exampleBlock]; 

Puede eliminar el bloque de la matriz cuando está bien para desasignarlo.

3. Trabaje en torno al problema de almacenamiento simplemente pasando el bloque

En lugar de administrar el almacenamiento y la destrucción de sus bloques, refactorice su código para que exampleBlock pase entre los diferentes methods hasta que termine su operación.

Alternativamente, puede usar NSBlockOperation para el código asíncrono y establecer su completionBlock para el código terminado de respuesta, y agregarlo a un NSOperationQueue.

Creo que la finalización se libera en su llamada asincrónica que podría estar causando el locking.

Sospecho que el problema no está en tu código sino en cualquier otro lugar.

Un posible problema es este:

IFF hay objects UIKit que se capturan en la completion del Bloque, posiblemente obtengas un error sutil cuando el bloque se ejecuta en un hilo no principal Y este bloque mantiene la última reference fuerte a esos objects UIKit:

Cuando completion finalización del bloque, su bloque se desasigna y, junto con esto, todas las variables importadas se "destruyen", lo que significa que, en el caso de pointers retenibles, recibirán un post de release . Si esta fue la última reference fuerte, el object capturado se desasignará, lo que ocurrirá en un hilo no principal, y esto puede ser fatal para los objects UIKit.

No veo nada malo con el código publicado y creo que el error está en otro lado.

Además, los bloques de anidamiento parecen innecesarios y complican la administración de la memory y probablemente dificulten la búsqueda de la causa del crash.

¿Por qué no comienzas moviendo el código en tu ExampleBlock directamente al bloque de completion ?

¿Qué pasa con esta solución: si llamará a un bloque más tarde que no está en el scope actual, entonces debería llamar a copyr para mover este bloque al montón de la stack

 - (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... }