He estado usando bloques desde hace un tiempo, pero creo que hay cosas que extraño sobre la gestión de la memoria en entornos ARC y no ARC. Siento que una comprensión más profunda me hará anular muchas pérdidas de memoria.Gestión de la memoria del objetivo C con bloques, ARC y no ARC
AFNetworking es mi principal uso de Blocks en una aplicación en particular. La mayoría de las veces, dentro de un controlador de finalización de una operación, hago algo como "[self.myArray addObject]".
Tanto en ARC como en entornos que no admiten ARC, se mantendrá "self" según this article from Apple.
Eso significa que cada vez que se invoca un bloque de finalización de una operación de red AFNetworking, el self se retiene dentro de ese bloque y se libera cuando ese bloque queda fuera del alcance. Creo que esto se aplica tanto a ARC como a otros. He ejecutado tanto la herramienta de Fugas como el Analizador Estático para que pueda encontrar cualquier pérdida de memoria. Ninguno mostró ninguno.
Sin embargo, no fue hasta hace poco que me encontré con una advertencia que no pude descifrar. Estoy usando ARC en este ejemplo particular.
Tengo dos variables de instancia que indican la finalización y el fracaso de una operación de red
@property (nonatomic, readwrite, copy) SFCompletionBlock completionBlock;
@property (nonatomic, readwrite, copy) SFFailureBlock failureBlock;
@synthesize failureBlock = _failureBlock;
@synthesize operation = _operation;
En algún lugar del código, hago esto:
[self.operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id
responseObject) {
NSError *error = [NSError errorWithDomain:@"com.test" code:100 userInfo:@{@"description": @"zero results"}];
_failureBlock(error);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"nothing");
}];
Xcode se queja de la línea que llama al failureBlock, con el mensaje "Capturing" self "fuertemente en este bloque es probable que dé como resultado un ciclo de retención. Creo que Xcode tiene razón: el bloque de fallas se conserva y tiene una copia propia del bloque, por lo que ninguna de las dos será desasignado.
Sin embargo, tengo las siguientes preguntas/observaciones.
1) Si cambio _failureBlock (error) a "self.failureBlock (error)" (sin comillas) el compilador deja de quejarse. ¿Porqué es eso? ¿Es esto una pérdida de memoria que el compilador falla?
2) En general, ¿cuál es la mejor práctica para trabajar con bloques en entornos habilitados con ARC y sin ARC cuando se usan bloques que son variables de instancia? Parece que en el caso de bloques de finalización y falla en AFNetworking, esos dos bloques son variables de instancia no, por lo que probablemente no entren en la categoría de retención de ciclos que describí anteriormente. Pero al usar bloques de progreso en AFNetworking, ¿qué se puede hacer para evitar retener ciclos como el anterior?
Me encantaría escuchar las opiniones de otras personas sobre ARC y no ARC con bloques y problemas/soluciones con la gestión de la memoria. Encuentro que estas situaciones son propensas a errores y creo que es necesario debatir sobre esto para aclarar las cosas.
No sé si importa, pero uso Xcode 4.4 con la última LLVM.
Gracias por esta respuesta. esto es mucha comida para pensar. En una nota lateral, lo que dijiste sobre la devolución de llamada y el hilo de fondo que causa fallas es correcto. Sin embargo, todos los bloques de finalización se llaman en el hilo principal en mi caso, para evitar tales problemas.En cuanto a la otra cosa, la solución de "devolver el bloque desde un método" para retener los ciclos, eso no resuelve el problema básico de crear un componente reutilizable donde la persona que llama decidirá el bloque de finalización. – csotiriou
Si está exponiendo una API con devoluciones de llamadas bloqueadas, no puede evitar que las personas que llaman creen sus propios ciclos de retención. Su API debería tener cuidado de liberar los bloques de devolución de llamada tan pronto como haya terminado con ellos, y probablemente debería exponer un método de cancelación que también liberará los bloques de devolución de llamada. En la práctica, la mayoría de las personas que llaman implementarán las devoluciones en línea de todos modos, no en propiedades. En ese caso, la semántica de la memoria es la misma que devolver el bloque desde un método: el ciclo de retención comienza cuando llaman a su API y finaliza cuando su API libera el bloque de devolución de llamada. –