ios - blocks objective c
Objective-C se bloquea en__destroy_helper_block_ (7)
¿Qué pasa con esta solución? Si más adelante llamará a un bloque que no está en el alcance actual, debería llamar a copy para mover este bloque al montón de la pila.
- (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...
}
Tengo una aplicación iOS que __destroy_helper_block_253
en llamadas como __destroy_helper_block_253
y __destroy_helper_block_278
y no estoy realmente seguro a qué se refiere "destroy_helper_block" o a qué número se supone que apunta.
¿Alguien tiene algún indicador sobre cómo localizar exactamente dónde podrían estar ocurriendo estos bloqueos?
Aquí hay un ejemplo de seguimiento (tenga en cuenta que las líneas con __destroy_helper_block
solo hacen referencia al archivo en el que está contenido y nada más, cuando normalmente también se incluiría 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
Edición 1: Este es un ejemplo de uno de los bloques definidos en el archivo donde se produce el bloqueo (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 archivo, pero la mayoría de ellos se proporcionan como argumentos a los métodos en lugar de ser definidos primero y luego referenciados más adelante.
Edición 2: Se agregó más contexto a la función anterior.
Cada fotograma de la traza de la pila debería darle una pista de lo que está haciendo libDispatch para provocar el bloqueo. Trabajando nuestro camino desde el fondo:
11 libsystem_pthread.dylib 0x000000018ffa16bc _pthread_wqthread
10 libdispatch.dylib 0x000000018fe134fc _dispatch_worker_thread2 + 76
Estas dos funciones hacen girar un hilo de trabajo y lo ejecutan. En el proceso, también configura un grupo de autorelease para el hilo.
9 libdispatch.dylib 0x000000018fe132b8 _dispatch_root_queue_drain + 556
Esta función señala el inicio del proceso de destrucción de la cola. El grupo de autorelease específico de subproceso se drena y, en el proceso, se liberan todas las variables a las que hace referencia esa cola en particular. Debido a que esto es libDispatch, eso significa que el objeto de máquina subyacente y el bloque de trabajo que envió deben irse ...
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 externo y debido a que contiene un objeto no trivial para destruir (otro bloque), el compilador 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 otro bit de destrucción no trivial por 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 causa raíz de todos sus problemas. Por alguna razón, ya sea que hayas capturado la cola a la que estás llamando o que hayas capturado un objeto que contiene una referencia a una cola débilmente, de modo que cuando se va por el camino del dinosaurio, lleva su cola. . Esto hace que libDispatch asuma que la cola está terminada y continúa desasignándose hasta que llega a la disposición específica del semáforo
0 libdispatch.dylib 0x000000018fe0eb2c _dispatch_semaphore_dispose + 60
Sin semáforo que liberar, Mach se quejará lo suficiente como para no devolver a KERN_SUCCESS
sobre la destrucción del semáforo, que es un error fatal en libDispatch. De hecho, abort()
en tal caso, bueno, técnicamente __builtin_trap()
, pero logran el mismo objetivo. Debido a que no hay un depurador adjunto, su aplicación desciende.
Entonces esto plantea la pregunta entonces: ¿cómo arreglas esto? Bueno, primero debe encontrar qué, si algo está haciendo referencia a un objeto de envío. Mencionó que estaba haciendo algunas redes asíncronas, por lo que ese sería el lugar para verificar primero. Si cualquiera de esos objetos contiene una cola o un semáforo, o hace referencia a un objeto que sí lo hace, y no lo está capturando con fuerza en ninguno de esos bloques, esto es precisamente lo que sucede cuando el bloque sale del ámbito junto con el objeto. .
Creo que la finalización se libera en su llamada asíncrona que podría estar causando el bloqueo.
Hipótesis:
-
doSomethingWithCompletion:
creaExampleBlock.
- Se inicia alguna operación de red asíncrona.
-
doSomethingWithCompletion:
devuelve, y se liberaExampleBlock
. - La operación de red asíncrona finaliza y llama a
ExampleBlock
.
En este caso, el puntero al bloque se eliminará de la referencia después de que se haya desasignado. (Quizás esto sea intermitente en función de si se ha agotado el grupo de autorelease, o si se han liberado otras áreas de memoria cercanas).
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. Almacenar 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 desasignarlo.
3. Resuelva el 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 distintos métodos hasta que finalice su operación.
Alternativamente, puede usar NSBlockOperation
para el código asíncrono y configurar su NSBlockOperation
de completionBlock
para el código de respuesta finalizada, y agregarlo a una NSOperationQueue.
No hay mucho que hacer aquí, pero mi sospecha es que el bloque nunca se mueve al montón. Los bloques se crean en la pila de forma predeterminada. El compilador a menudo puede decidir cuándo moverlos al montón, pero la forma en que lo está entregando de bloque a bloque probablemente nunca lo haga.
Yo añadiría una completionCopy = [completion copy]
para forzarla en el montón. Luego trabajar con la completionCopy
. Vea la respuesta de bbum sobre el almacenamiento de bloques en diccionarios. Con ARC, no necesita llamar más a Block_copy()
y Block_release()
, pero creo que todavía quiere llamar a -copy
aquí.
No veo nada malo con el código publicado y creo que el error está en otra parte.
También los bloques de anidamiento parecen innecesarios y complican la administración de la memoria y probablemente dificultan la búsqueda de la causa del bloqueo.
¿Por qué no comienza moviendo el código en su ExampleBlock
directamente al bloque de completion
?
Yo sospecharía, el problema no está en su código sino en otro lugar.
Un posible problema es este:
IFF hay objetos UIKit que se capturan en la completion
del Bloque, posiblemente obtenga un error sutil cuando el bloque se ejecuta en un hilo no principal Y este bloque mantiene la última referencia segura a esos objetos UIKit:
Cuando completion
finalización del Bloque, el bloque se desasigna y, junto con esto, todas las variables importadas se "destruyen", lo que significa que, en el caso de los punteros retomables, reciben un mensaje de release
. Si esta fue la última referencia segura, el objeto capturado se desasigna, lo que ocurrirá en un subproceso no principal, y esto puede ser fatal para los objetos UIKit.