iOS: la propiedad de bloque establece directamente los bloqueos cuando se accede
-
27-10-2019 - |
Pregunta
Considere el siguiente código:
@interface ClassA : NSObject
@property (nonatomic, copy) void(^blockCopy)();
@end
@implementation ClassA
@synthesize blockCopy;
- (void)giveBlock:(void(^)())inBlock {
blockCopy = inBlock;
}
@end
Luego úsalo en una clase que tenga un strong
Propiedad del tipo ClassA
llamó someA
:
self.someA = [[ClassA alloc] init];
[self.someA giveBlock:^{
NSLog(@"self = %@", self);
}];
dispatch_async(dispatch_get_main_queue(), ^{
self.someA.blockCopy();
self.someA = nil;
});
Si ejecuto eso construido O3
con arco habilitado, en iOS, se bloquea durante el self.someA.blockCopy();
llamar objc_retain
. ¿Por qué?
Ahora me doy cuenta de que la gente probablemente va a decir que debería configurarlo con self.blockCopy = inBlock
Pero pensé que Arc debería estar haciendo lo correcto aquí. Si miro el ensamblaje (ARMV7) producido por el giveBlock:
método se ve así:
.align 2
.code 16
.thumb_func "-[ClassA giveBlock:]"
"-[ClassA giveBlock:]":
push {r7, lr}
movw r1, :lower16:(_OBJC_IVAR_$_ClassA.blockCopy-(LPC0_0+4))
mov r7, sp
movt r1, :upper16:(_OBJC_IVAR_$_ClassA.blockCopy-(LPC0_0+4))
LPC0_0:
add r1, pc
ldr r1, [r1]
add r0, r1
mov r1, r2
blx _objc_storeStrong
pop {r7, pc}
Que esta llamando objc_storeStrong
que a su vez hace un retain
en el bloque y un release
en el viejo bloque. Supongo que ARC no se da cuenta correctamente de que es una propiedad de bloque, ya que creo que debería llamar objc_retainBlock
en lugar de lo normal objc_retain
.
O, ¿estoy totalmente equivocado y en realidad está haciendo lo que documenta y lo he leído de la manera incorrecta?
Discusión muy bienvenida en esto: encuentro esto bastante intrigante.
Puntos a tener en cuenta:
- No se bloquea en OS X.
- No se bloquea construido
O0
.
Solución
- (void)giveBlock:(void(^)())inBlock {
blockCopy = inBlock;
}
Debe copiar el bloque en la asignación o cuando se pasa a esta función. Si bien ARC resuelve el problema de auto-movido a retorno, no lo hace para los argumentos (no se puede hacer a las idiosincrasias de C).
Que no se estrella en ciertos entornos es simplemente una coincidencia; No se bloqueará mientras la versión de la pila del bloque no se haya sobrescribida. Una señal segura de esto es cuando tienes un bloqueo que desaparece con la optimización desactivada. Con la optimización apagada, el compilador no reutilizará la memoria de la pila dentro de un alcance dado, lo que hace que la memoria sea "válida" por mucho tiempo después de que sea.
Todavía no entiendo por qué no puede hacer un objc_blocketrain en lugar de un objc_etain normal. El compilador conoce el tipo después de todo.
Estoy bastante seguro de que el problema es el costo potencial de la tarea. Si el bloque captura mucho estado, incluidos, potencialmente, otros bloques, entonces el block_copy () podría ser realmente De Verdad De Verdad caro.
Es decir, si tuvieras algo como:
BlockType b = ^(...) { ... capture lots of gunk ... };
SomeRandomFunc(b);
... y eso implicaba un block_copy () simplemente debido a la tarea, haría imposible usar bloques de manera consistente sin riesgo de problemas de rendimiento patológico. Porque no hay forma de que el compilador sepa si SomeRandomFunc()
es sincrónico o asincrónico, no hay forma de administrar esto automáticamente (en este momento, estoy seguro de deshacerse de este posible triplewire).