UIViewController purgeMemoryForReason: Bloqueo en iOS 5

Así que he estado viendo este locking con bastante frecuencia en Crashlytics, tanto en iPad como en iPad 2, ejecutando iOS 5. Parece que es causado por una advertencia de memory, pero el rastreo de stack no hace reference a ninguno de mis códigos de aplicación, solo iOS frameworks:

0 libobjc.A.dylib objc_msgSend + 15 1 UIKit -[UIViewController purgeMemoryForReason:] + 64 2 Foundation __57-[NSNotificationCenter addObserver: selector: name: object:]_block_invoke_0 + 18 3 CoreFoundation ___CFXNotificationPost_block_invoke_0 + 70 4 CoreFoundation _CFXNotificationPost + 1406 5 Foundation -[NSNotificationCenter postNotificationName: object: userInfo:] + 66 6 Foundation -[NSNotificationCenter postNotificationName: object:] + 30 7 UIKit -[UIApplication _performMemoryWarning] + 80 8 UIKit -[UIApplication _receivedMemoryNotification] + 174 9 libdispatch.dylib _dispatch_source_invoke + 516 10 libdispatch.dylib _dispatch_queue_invoke + 50 11 libdispatch.dylib _dispatch_main_queue_callback_4CF + 156 12 CoreFoundation __CFRunLoopRun + 1268 13 CoreFoundation CFRunLoopRunSpecific + 300 14 CoreFoundation CFRunLoopRunInMode + 104 15 GraphicsServices GSEventRunModal + 156 16 UIKit UIApplicationMain + 1090 17 500px iOS main.m line 12 

He buscado en Google alto y bajo, pero no puedo encontrar ninguna solución a esto. Parece que esto es causado por la liberación excesiva de una instancia de UIViewController, pero estoy usando ARC, así que no veo cómo podría ser ese el caso.

No sé cómo abordar esto. Ni siquiera puedo decir qué subclass UIViewController está causando el problema. He intentado reproducir el problema en el simulador y en el dispositivo, pero no puedo encontrar lo que lo causa. ¿Alguien ha visto algo así o tiene sugerencias sobre cómo abordar la reproducción del problema?

Creo que he resuelto el problema. Estaba pensando en ello, y el problema no es la descarga de la vista UIViewController, es la publicación de la notificación de advertencia de memory baja real. Hay varias instancias en mi código donde [[NSNotificationCenter defaultCenter] removeObserver:self] . Esto está bien en el método dealloc, pero hubo dos casos de esto en los methods viewDidUnload .

Noté esto cuando mi breakpoint en didReceiveMemory de uno de UIViewController's no fue alcanzado. El código en viewDidUnload también estaba desregistrándose de otras notifications del sistema, como se detalla aquí .

No voy a marcar esto como una respuesta aceptada hasta que verifique que los lockings se detengan con la nueva actualización.

ACTUALIZACIÓN: ¡He verificado con Crashlytics que el problema se ha solucionado!

Noté la huella exacta de la misma stack en los lockings informados por HockeyApp para los dispositivos que se ejecutan en iOS 5.

Nunca llamé a [[NSNotificationCenter defaultCenter] removeObserver:self] excepto dentro de dealloc, por lo que esta no pudo ser la causa del locking.

Aquí es cómo pude reproducir el fallo: desde MasterViewController , DetailViewController , luego lo DetailViewController presionando el button Atrás. Finalmente, disparo una advertencia de memory y ocurre el locking (en iOS 5 solamente).

Resulta que la instancia de DetailViewController no se libera después de haber sido eliminada debido a un ciclo de retención cuando se utiliza SVPullToRefresh:

 @implementation DetailViewController - (void) viewDidLoad { [super viewDidLoad]; [self.scrollView addPullToRefreshWithActionHandler:^{ [self refresh]; }]; } @end 

Dado que DetailViewController no se ha lanzado, todavía está registrado para notifications de advertencia de memory y esto es lo que sucede:

 frame #0: 0x0004d61b MyApp`-[DetailViewController dealloc](self=0x089a5150, _cmd=0x024d2738) + 27 at DetailViewController.m:103 frame #1: 0x0227ae3d libobjc.A.dylib`_objc_rootRelease + 47 frame #2: 0x0227ae00 libobjc.A.dylib`objc_release + 48 frame #3: 0x0227c047 libobjc.A.dylib`objc_storeStrong + 39 frame #4: 0x0004e44c MyApp`__destroy_helper_block_ + 44 at DetailViewController.m:157 frame #5: 0x029b555d libsystem_sim_blocks.dylib`_Block_release + 166 frame #6: 0x0227ae00 libobjc.A.dylib`objc_release + 48 frame #7: 0x0227c047 libobjc.A.dylib`objc_storeStrong + 39 frame #8: 0x00084c8d MyApp`-[SVPullToRefreshView .cxx_destruct](self=0x08bf3af0, _cmd=0x00000001) + 525 at UIScrollView+SVPullToRefresh.m:121 frame #9: 0x0226630d libobjc.A.dylib`object_cxxDestructFromClass + 104 frame #10: 0x02270fde libobjc.A.dylib`objc_destructInstance + 38 frame #11: 0x02271015 libobjc.A.dylib`object_dispose + 20 frame #12: 0x0247a9a1 CoreFoundation`-[NSObject dealloc] + 97 frame #13: 0x00a8cdc7 UIKit`-[UIView dealloc] + 748 frame #14: 0x0227ae3d libobjc.A.dylib`_objc_rootRelease + 47 frame #15: 0x00a90b73 UIKit`-[UIView(Hierarchy) removeFromSuperview] + 194 frame #16: 0x00a8cc10 UIKit`-[UIView dealloc] + 309 frame #17: 0x00a9d6ff UIKit`-[UIScrollView dealloc] + 405 frame #18: 0x013ab36c Foundation`NSKVODeallocate + 105 frame #19: 0x0227ae3d libobjc.A.dylib`_objc_rootRelease + 47 frame #20: 0x00b21c12 UIKit`-[UIViewController setView:] + 447 frame #21: 0x00b21885 UIKit`-[UIViewController unloadViewForced:] + 117 frame #22: 0x00b2180b UIKit`-[UIViewController unloadViewIfReloadable] + 41 frame #23: 0x00b256ff UIKit`-[UIViewController purgeMemoryForReason:] + 75 frame #24: 0x00b2563b UIKit`-[UIViewController didReceiveMemoryWarning] + 41 frame #25: 0x00b2560d UIKit`-[UIViewController _didReceiveMemoryWarning:] + 33 frame #26: 0x0141ca29 Foundation`__57-[NSNotificationCenter addObserver:selector:name:object:]_block_invoke_0 + 40 frame #27: 0x02443855 CoreFoundation`___CFXNotificationPost_block_invoke_0 + 85 frame #28: 0x02443778 CoreFoundation`_CFXNotificationPost + 1976 frame #29: 0x0136119a Foundation`-[NSNotificationCenter postNotificationName:object:userInfo:] + 98 frame #30: 0x0136db03 Foundation`-[NSNotificationCenter postNotificationName:object:] + 55 frame #31: 0x00a64cf4 UIKit`-[UIApplication _performMemoryWarning] + 91 frame #32: 0x00a64e00 UIKit`-[UIApplication _receivedMemoryNotification] + 180 frame #33: 0x00a64f98 UIKit`__block_global_0 + 36 frame #34: 0x029f1450 libdispatch.dylib`_dispatch_source_invoke + 719 frame #35: 0x029edcc4 libdispatch.dylib`_dispatch_queue_invoke + 66 frame #36: 0x029ee4cf libdispatch.dylib`_dispatch_main_queue_callback_4CF + 295 frame #37: 0x023af803 CoreFoundation`__CFRunLoopRun + 2003 frame #38: 0x023aed84 CoreFoundation`CFRunLoopRunSpecific + 212 frame #39: 0x023aec9b CoreFoundation`CFRunLoopRunInMode + 123 frame #40: 0x038d07d8 GraphicsServices`GSEventRunModal + 190 frame #41: 0x038d088a GraphicsServices`GSEventRun + 103 frame #42: 0x00a5a626 UIKit`UIApplicationMain + 1163 frame #43: 0x00002b82 MyApp`main(argc=1, argv=0xbffff318) + 178 at main.m:15 

O en inglés: la instancia SVPullToRefreshView se libera como resultado de la vista que se descarga. Como la instancia de SVPullToRefreshView es el último object que contiene una reference al DetailViewController , se libera y luego se desasigna. Pero purgeMemoryForReason: todavía estaba haciendo cosas (es decir, accediendo a variables de instancia) con el controller de vista desasignado, de ahí el locking.

Una vez diagnosticado, la solución fue muy simple: solo evite el ciclo de retención en primer lugar.

 @implementation DetailViewController - (void) viewDidLoad { [super viewDidLoad]; __typeof__(self) __weak weakSelf = self; [self.scrollView addPullToRefreshWithActionHandler:^{ [weakSelf refresh]; }]; } @end