6

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.

Respuesta

6

1) Si cambio _failureBlock (error) para "self.failureBlock (error)" (sin comillas) el compilador se detiene en las protestas. ¿Porqué es eso? ¿Es esto una pérdida de memoria que el compilador falla?

El ciclo de retención existe en ambos casos. Si usted está apuntando iOS 5+, puede pasar en una referencia débil mental:

__weak MyClass *weakSelf; 
[self.operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { 
    NSError *error = [NSError errorWithDomain:@"com.test" code:100 userInfo:@{@"description": @"zero results"}]; 
    if (weakSelf.failureBlock) weakSelf.failureBlock(error); 
} failure:^(AFHTTPRequestOperation *operation, NSError *error) { 
    NSLog(@"nothing"); 
}]; 

Ahora mismo no será retenido, y si se cancela la asignación antes de la devolución de llamada se invoca, la devolución de llamada es un no- op. Sin embargo, es posible que se encuentre en desasignación mientras se invoca la devolución de llamada en una cadena de fondo, por lo que este patrón puede generar bloqueos ocasionales.

2) En general, ¿cuál es la mejor práctica para trabajar con bloques en tanto ARC y no ARC ambientes habilitados si se utilizan bloques que son variables de instancia? Parece que en el caso de completar bloques en AFNetworking, esos dos bloques no son variables de instancia, por lo que probablemente no caigan en la categoría de retención de ciclos que I descrita anteriormente. Pero al usar bloques de progreso en AFNetworking, , ¿qué se puede hacer para evitar retener ciclos como el anterior?

La mayoría de las veces, creo it's better not to store blocks in instance variables. Si en su lugar devuelve el bloque de un método de su clase, seguirá teniendo un ciclo de retención, pero solo existe desde el momento en que se invoca el método hasta el momento en que se libera el bloque. Por lo tanto, evitará que su instancia sea desasignada durante la ejecución del bloque, pero el ciclo de retención finaliza cuando se libera el bloque:

-(SFCompletionBlock)completionBlock { 
    return ^(AFHTTPRequestOperation *operation , id responseObject) { 
     [self doSomethingWithOperation:operation]; 
    }; 
} 

[self.operation setCompletionBlockWithSuccess:[self completionBlock] 
             failure:[self failureBlock] 
]; 
+0

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

+0

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. –

2

Eso significa que cada vez que un bloque de terminación de una red AFNetworking operación se llama, la auto queda retenido dentro de ese bloque, y se libera cuando ese bloque sale del ámbito.

No, self es conservado por el bloque cuando se crea el bloque.Y se libera cuando el bloque es desasignado.

creo Xcode tiene razón: el bloque retiene el fracaso auto y auto lleva a cabo su propia copia del bloque, por lo que ninguno de los dos se desasignar.

El bloque en cuestión que conserva self es el bloque de finalización pasado a setCompletionBlockWithSuccess. self no contiene una referencia a este bloque. Más bien, self.operation (presumiblemente algún tipo de NSOperation) conserva los bloques mientras se está ejecutando. Entonces hay temporalmente un ciclo. Sin embargo, cuando la operación se termina de ejecutar, el ciclo se interrumpirá.

1) Si cambio _failureBlock (error) para "self.failureBlock (error)" (sin comillas) el compilador se detiene en las protestas. ¿Porqué es eso? ¿Es esto una pérdida de memoria que el compilador falla?

No debería haber diferencia. self se captura en ambos casos. El compilador no garantiza capturar todos los casos de retención de ciclos.