ios objective-c-blocks weak-references retain-cycle

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 .

Ver último comentario aquí


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:

  1. Usando un puntero cualificado __weak de auto

    UsersViewController* __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];

  2. Usando un __block puntero calificado de auto y eventualmente configurándolo como nil 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