ios - Usando un yo débil en la función dispatch_async
objective-c-blocks weak-references (2)
Leí muchas publicaciones sobre el uso de __weak self
inside dispatch_async
, y ahora estoy un poco confundido.
si tengo :
self.myQueue = dispatch_queue_create("com.biview.core_data", NULL);
dispatch_async(self.myQueue, ^(void){
if (!self.var1) {
self.var1 = ...;
}
dispatch_async(dispatch_get_main_queue(), ^(void) {
if ([self.var2 superview]) {
[self.var2 removeFromSuperview];
}
[self.Label setText:text];
});
});
¿Necesito usar __weak self
? Porque leí que en algunos casos dispatch_async
no necesita un __weak self
.
Actualización rápida:
Un ejemplo de este llamado baile fuerte-débil en veloz:
Swift 4.2:
func doSomeThingAsynchronously() {
DispatchQueue.global().async {
// Do task in default queue
DispatchQueue.main.async { [weak self] in
// Do task in main queue
guard let self = self else { return }
self.updateView()
}
}
}
Swift 3 y 4:
func doSomeThingAsynchronously() {
DispatchQueue.global().async {
// Do task in default queue
DispatchQueue.main.async { [weak self] in
// Do task in main queue
guard let strongSelf = self else { return }
strongSelf.updateView()
}
}
}
Swift 2:
func doSomeThingAsynchronously() {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> () in
// Do task in default queue
dispatch_async(dispatch_get_main_queue(), { [weak self] () -> () in
guard let strongSelf = self else { return }
// Do task in main queue
strongSelf.updateView()
})
}
}
El popular proyecto de código abierto Alamofire
utiliza este enfoque.
Extienda la vida útil de los objetos utilizando el [yo débil] y la guardia permite que el lenguaje strongSelf = self else {return}.
Para más información echa un vistazo a swift-style-guide
Suponiendo que self es un puntero de objeto a un UIViewController
.
Cosas para considerar:
Un
UIViewController
es un objeto "UIKit". Los objetos UIKit no deben enviarse métodos en subprocesos no principales, es decir, ¡esos métodos deben ejecutarse solo en el subproceso principal!Un bloque que se haya puesto en cola en una cola, ya sea de forma síncrona o asíncrona, se ejecutará eventualmente, ¡sin importar qué! Bueno, a menos que el programa termine antes de que esto pueda suceder.
Los punteros fuertes retenidos capturados se retendrán cuando se copie el bloque (por ejemplo, cuando se envíen de forma asíncrona), y se liberarán de nuevo cuando se destruirá el bloque (después de que haya finalizado).
Los punteros débiles que se pueden volver a capturar NO se retendrán ni se liberarán.
En su escenario, donde se captura a sí mismo en el bloque que se envía en la cola principal , no debe preocuparse de que sucedan cosas malas.
¿Entonces por qué? ¿Y qué pasa en realidad?
Como self se capturará en el bloque que se envía de forma asíncrona , se retendrá implícitamente y se liberará nuevamente cuando el bloque haya finalizado.
Eso significa que la vida útil del yo se prolongará hasta después de que finalice el bloque. Tenga en cuenta que su segundo bloque se distribuye en el subproceso principal y se garantiza que el self sigue vivo cuando se ejecuta ese bloque.
Esta "vida extendida" más arriba, podría ser una característica deseada de su programa.
Si explícitamente no desea extender la vida UIViewController
objeto UIViewController
, y en su lugar desea que el bloque, cuando finalmente se ejecute, verifique si este objeto UIViewController
todavía existe, puede usar un puntero __weak. Tenga en cuenta que el bloque se ejecuta eventualmente, sin importar si el UIViewController
todavía está vivo o si ha sido desasignado UIViewController
tanto.
Es posible que desee que el bloque no haga "nada" si el UIViewController
ha sido desasignado antes de que se ejecute el bloqueo:
MyController* __weak weakSelf = self;
dispatch_async(queue, ^{
MyController* strongSelf = weakSelf;
if (strongSelf) {
...
}
else {
// self has been deallocated in the meantime.
}
});
Ver también: Transición a las notas de la versión ARC
Recuerde: los objetos UIKit
no deben enviarse métodos en subprocesos no principales.
Otro error sutil puede ocurrir debido al hecho de que los objetos UIKit
ejecutarán métodos solo en el hilo principal.
Esto puede violarse si un bloque captura un objeto UIKit
que se distribuye de forma asíncrona y se ejecuta en un subproceso no principal . Entonces puede suceder que el bloque contenga la última referencia UIKit
a ese objeto UIKit
. Ahora, cuando el bloque se ejecute finalmente, el bloque se destruirá y el objeto UIKit
se liberará. Ya que esta es la última referencia UIKit
objeto UIKit
, será desasignado. Sin embargo, esto sucede en el hilo donde se ha ejecutado el bloque, ¡y este no es el hilo principal! Ahora, pueden suceder (y ocurrirán) cosas malas, ya que el método dealloc
sigue siendo un método enviado a un objeto UIKit
.
Puedes evitar este error enviando un bloque que captura un puntero fuerte a ese objeto UIKit y envíale un método ficticio:
UIViewController* strongUIKitPointer = ...
dispatch_async(non_main_queue, ^{
... // do something
dispatch(dispatch_get_main_queue(), ^{
[strongUIKitPointer self]; // note: self is a method, too - doing nothing
});
});
Sin embargo, en su escenario, la última referencia segura podría estar solo en el bloque que se ejecuta en el hilo principal. Entonces, estás a salvo de este sutil error. ;)
Editar:
En su configuración, nunca tiene un ciclo de retención. Se produce un ciclo de retención si un objeto retenible A hace una fuerte referencia a otro objeto retenible B, y el objeto B hace una fuerte referencia a A. Tenga en cuenta que un "Bloque" también es un objeto retenible.
Un ejemplo artificial con una referencia cíclica:
typedef void(^my_completion_block_t)(NSArray* result);
@interface UsersViewController : UIViewController
@property (nonatomic, copy) my_completion_block_t completion;
@property (nonatomic) NSArray* users;
@end
Aquí, tenemos una terminación de propiedad cuyo tipo de valor es un Bloque. Es decir, obtenemos un ivar con el nombre _completion
cuyo tipo es un bloque.
Un cliente puede establecer un controlador de finalización al que se debe llamar cuando una determinada operación haya finalizado. Supongamos que la operación obtiene una lista de Usuarios de un servidor remoto. El plan es establecer los usuarios de la propiedad una vez finalizada la operación:
El enfoque descuidado introduciría accidentalmente una referencia cíclica:
En algún lugar en "UsersViewController.m"
self.completion = ^(NSArray* users){
self.users = users;
}
[self fetchUsers]; // start asynchronous task
Aquí, el yo tiene una fuerte referencia al _completion de _completion
, que es un bloque. Y el propio bloque se captura a sí mismo , lo que hace que se retenga cuando el bloque se copia cuando se envía. Este es un ciclo de referencia clásico.
Para evitar esa referencia cíclica, tenemos algunas alternativas:
Usando un puntero cualificado
__weak
de autoUsersViewController* __weak weakSelf = self; self.completion = ^(NSArray* users) { UsersViewController* strongSelf = weakSelf; if (strongSelf) { strongSelf.users = users; } else { // the view controller does not exist anymore } } [usersViewController fetchUsers];
Usando un
__block
puntero calificado de auto y eventualmente configurándolo comonil
en el bloque cuando termina:UsersViewController* __block blockSelf = self; self.completion = ^(NSArray* users) { blockSelf.users = users; blockSelf = nil; } [usersViewController fetchUsers];
Ver también: Transición a las notas de la versión ARC