¿Por qué una fuerte reference al UIViewController padre en performBatchUpdates pierde una actividad?

Acabo de terminar de depurar una fuga de UIViewController muy desagradable, de tal manera que el UIViewController no fue desocupado incluso después de llamar a dismissViewControllerAnimated .

Localicé el problema al siguiente bloque de código:

  self.dataSource.doNotAllowUpdates = YES; [self.collectionView performBatchUpdates:^{ [self.collectionView reloadItemsAtIndexPaths:@[indexPath]]; } completion:^(BOOL finished) { self.dataSource.doNotAllowUpdates = NO; }]; 

Básicamente, si hago una llamada a performBatchUpdates e inmediatamente llamo dismissViewControllerAnimated , el UIViewController se filtra y el método dealloc de ese UIViewController nunca se llama. UIViewController se cuelga para siempre.

¿Alguien puede explicar este comportamiento? Supongo que performBatchUpdates ejecuta durante un intervalo de time, digamos, 500 ms, por lo que supondría que después de dicho intervalo, llamaría a estos methods y luego activaría el displace.

La solución parece ser esta:

  self.dataSource.doNotAllowUpdates = YES; __weak __typeof(self)weakSelf = self; [self.collectionView performBatchUpdates:^{ __strong __typeof(weakSelf)strongSelf = weakSelf; if (strongSelf) { [strongSelf.collectionView reloadItemsAtIndexPaths:@[indexPath]]; } } completion:^(BOOL finished) { __strong __typeof(weakSelf)strongSelf = weakSelf; if (strongSelf) { strongSelf.dataSource.doNotAllowUpdates = NO; } }]; 

Tenga en count que la variable de miembro de BOOL , doNotAllowUpdates , es una variable que agregué que impide cualquier tipo de actualizaciones de dataSource / collectionView mientras se ejecuta una llamada a performBatchUpdates.

Busqué en torno a la discusión en línea sobre si debemos usar el patrón weakSelf / strongSelf en performBatchUpdates , pero no encontramos nada específicamente en esta pregunta.

Estoy feliz de haber podido llegar al final de este error, pero me encantaría que un desarrollador de iOS más inteligente me explicara este comportamiento que estoy viendo.

Como se dio count, cuando no se usa weak , se crea un ciclo de retención.

El ciclo de retención es causado por tener una fuerte reference a collectionView y collectionView ahora tiene una fuerte reference a self .

Uno siempre debe asumir que ese self podría haber sido desasignado antes de que se ejecute un bloque asíncrono. Para manejar esto con security, se deben hacer dos cosas:

  1. Siempre use una reference débil a self (o al propio ivar)
  2. Siempre confirme que existe el weakSelf antes de pasarlo como un nunnull

ACTUALIZAR:

Poner un poco de logging en performBatchUpdates confirma mucho:

 - (void)logPerformBatchUpdates { [self.collectionView performBatchUpdates:^{ NSLog(@"starting reload"); [self.collectionView reloadItemsAtIndexPaths:[self.collectionView indexPathsForVisibleItems]]; NSLog(@"finishing reload"); } completion:^(BOOL finished) { NSLog(@"completed"); }]; NSLog(@"exiting"); } 

huellas dactilares:

 starting reload finishing reload exiting completed 

Esto muestra que el bloque de finalización se dispara DESPUÉS de abandonar el scope actual, lo que significa que se envía de forma asíncrona al hilo principal.

Menciona que descarta inmediatamente el controller de vista después de realizar la actualización por lotes. Creo que esta es la raíz de su problema:

Después de algunas testings, la única manera en que pude recrear la pérdida de memory fue despachando el trabajo antes de descartarlo. Es muy remota, pero ¿su código se ve así por casualidad ?:

 - (void)breakIt { // dispatch causes the view controller to get dismissed before the enclosed block is executed dispatch_async(dispatch_get_main_queue(), ^{ [self.collectionView performBatchUpdates:^{ [self.collectionView reloadItemsAtIndexPaths:[self.collectionView indexPathsForVisibleItems]]; } completion:^(BOOL finished) { NSLog(@"completed: %@", self); }]; }); [self.presentationController.presentingViewController dismissViewControllerAnimated:NO completion:nil]; } 

El código anterior conduce a que no se dealloc a dealloc en el controller de vista.

Si toma su código existente y simplemente envía (o realiza un selector: después de 🙂 la llamada dismissViewController probablemente solucionará el problema.

Esto parece un error con UICollectionView. Los usuarios de API no deberían esperar que los parameters de bloques de un solo funcionamiento se conserven más allá de la ejecución de la tarea, por lo que evitar ciclos de reference no debería ser un problema.

UICollectionView debería aclarar las references a los bloques una vez que haya finalizado el process de actualización por lotes, o si el process de actualización por lotes se interrumpe (por ejemplo, al eliminar la vista de la colección de la pantalla).

Has visto por ti mismo que el bloque de finalización se invoca incluso si la vista de la colección se quita de la pantalla durante el process de actualización, por lo que la vista de la colección debería estar despejando cualquier reference que tenga a ese bloque de finalización: nunca se llamará nuevamente, independientemente del estado actual de la vista de colección.