2012-06-12 11 views
19

Tengo una aplicación MonoTouch que tiene un UITabBarController, con cada una de las pestañas siendo un UINavigationController. Algunos de estos envuelven un UIViewController que agrega una UITableView y una UIToolbar, y otros envuelven un DialogViewController.Administración de memoria/recursos usando MonoTouch y MonoTouch.Dialog

No he prestado mucha atención a la gestión de memoria/vista hasta el momento (he estado funcionando principalmente en el simulador), pero cuando comencé a probar en un dispositivo real, he notado algunas fallas debido a condiciones de poca memoria (por ejemplo, la aplicación se termina, y descubrí en mi registro que se llamó a DidReceiveMemoryWarning antes de esto). Otras veces noto pausas prolongadas en la capacidad de respuesta de la aplicación que, supongo, se deben a un ciclo de GC.

Hasta ahora he asumido que cada DialogViewController que introduzco en la pila de navegación limpiará sus vistas y otras cosas que se asignan cuando lo muestre. Pero estoy empezando a darme cuenta de que probablemente no sea tan fácil, y que necesito comenzar a llamar a Dispose() sobre cosas.

¿Existen mejores prácticas sobre cómo gestionar los recursos y la memoria con MonoTouch y MT.D? Específicamente:

  • ¿Es necesario llamar a Dispose en un DialogViewController después de que se ha reventado? Si es así, ¿dónde es mejor hacer esto? (ViewDidUnload? DidReceiveMemoryWarning? Destructor?)
  • ¿El DVC elimina automáticamente objetos como el RootElement que se le pasa o tengo que preocuparme por esto? ¿Qué hay de UIImage que se carga como parte de la representación de una celda de tabla (por ejemplo, StyledStringElement)?
  • ¿Hay lugares en los que deba llamar a GC.Collect() para espaciar mejor las colecciones para no tener un poco de capacidad de respuesta cuando ocurre un GC?
  • ¿El recolector de basura generacional ayuda con los problemas de interactividad y es lo suficientemente estable como para usar en una aplicación de producción? (Creo que aún se anuncia como "experimental" en MonoDevelop 3.0.2/MT 4.3.3)
  • ¿Qué debo hacer en DidReceiveMemoryWarning para reducir la probabilidad de que iOS filme mi aplicación? Dado que cada controlador de vista no visible parece recibir esta llamada, supongo que debería limpiar los recursos del controlador de vista ... ¿debería hacer el mismo tipo de cosas que hago en ViewDidUnload?
  • Parece que no me llama mi ViewDidUnload (incluso después de recibir un DidReceiveMemoryWarning). De hecho, no recuerdo haberlo visto nunca en mi registro. Si iOS siempre llamaba mi ViewDidUnload después de DidReceiveMemoryWarning, podía hacer toda la limpieza en ViewDidUnload ... ¿Cuál es la mejor manera de dividir la responsabilidad de limpieza entre ViewDidUnload y DidReceiveMemoryWarning?

Me disculpo por la naturaleza general de esta pregunta - esto parece un buen tema para un libro blanco, pero no pude encontrar ninguna ...

actualización: hacer la pregunta más concreta : después de usar Instruments y el generador de perfiles Xamarin Heapshot, me queda claro que estoy filtrando UIViewControllers cuando el usuario saca la pila de navegación. Rolf archivó bug para esto y tiene dos dups, por lo que este es un problema real para algo más que para mí. Lamentablemente, no he encontrado una buena solución para los UIViewControllers filtrados: no he encontrado un buen lugar para llamar a Dispose() sobre ellos. El lugar natural para liberar recursos asignados por ViewDidLoad se encuentra en el mensaje ViewDidUnload, pero nunca se llama en el simulador, por lo que mi huella de memoria sigue creciendo.En el dispositivo, veo DidReceiveMemoryWarning, pero soy reacio a usar esto como el lugar para liberar mi viewcontroller y sus recursos, ya que no estoy seguro de que iOS realmente descargue mi vista, y por lo tanto no estoy seguro de que mi ViewDidLoad vuelva a ser llamado cualquiera de los dos (lo que lleva a un ViewDidAppear que necesitaría un código defensivo frente a las situaciones en las que se eliminaron sus recursos subyacentes). Me gustaría obtener algunos consejos sobre cómo salir de este lío ...

Respuesta

30

He pasado un par de días en el código fuente de MT.D y en el generador de perfiles. Mientras que todavía estoy buscando una orientación general sobre cuál es el mejor patrón de diseño es para la implementación de DidReceiveMemoryWarning y ViewDidUnload, yo tengo algunas observaciones generales para compartir que podría ser útil para alguien:

  1. MonoTouch.Dialog se comporta muy bien. No pierde ningún recurso bajo uso ordinario. Mantiene un árbol de control bajo DVC.Root, y el método de eliminación de cada elemento elimina correctamente el control UIKit subyacente. Ni siquiera tiene que preocuparse por deshacerse de un RootElement antiguo si reemplazó DVC.Root: el creador de propiedades lo descarta automáticamente. En general, MT.D no parece sufrir problemas significativos de memoria. Hay una excepción - ver abajo.
  2. Al crear sus propios Elementos personalizados (por ejemplo, MultilineEntryElement), asegúrese de anular el método Dispose (bool), eliminar el control UIKit subyacente (por ejemplo, UITextView) y encadenar el método Dispose() de la clase base. El código fuente en el proyecto MT.D github de Miguel proporciona muchos buenos ejemplos. Todos los Elementos implementan el patrón Dispose estándar (aunque omiten un destructor/finalizador que llama a Dispose (false)).
  3. Al implementar controladores de vista personalizados, generalmente no es necesario implementar Dispose en las subclases UIViewController, ni en TableView DataSource ni en las clases Delegate. Cuando el controlador de vista obtiene GC, llamará correctamente a Dispose en sus referencias. Todas las celdas que asigne en el DataSource se eliminarán correctamente.
  4. Como excepción a (3): me encontré con un problema desagradable al agregar mi propia subvista a la celda de una TableView. Esta subvista es un control que creé llamado "UICheckbox" que finalmente hereda de UIImageView, que tiene dos UIImages (activado y desactivado) y un evento público llamado Clicked. Solo experimento un problema cuando un manejador de eventos que hace referencia a miembros de DataSource está enganchado a este evento (si el controlador de eventos no hace referencia al DataSource o al controlador en sí, todo está bien). Sin embargo, cuando se cumplen las condiciones anteriores, y el controlador se descarta, aparentemente hay algún ciclo que el GC no puede descifrar, y cada UICheckbox que puse en el TableView se filtró (junto con sus imágenes). La única forma que encontré para solucionar esto fue agregar código a ViewDidDisappear para deshacerse del ViewController y limpiar su estado IFF. Ya no está en ninguna parte de la pila de navegación. Es raro pero funciona.
  5. En general, me adhiero a la siguiente plantilla para la asignación de objetos en mis controladores de vista:

    • asignar nada en el constructor (sólo lo utiliza para pasar del estado en)
    • crear un árbol de control en viewDidLoad (y deséchelo en ViewDidUnload). pensar "InitializeComponent" en XAML (si eso ayuda). Si el UIViewController va a insertar un DialogViewController en la pila de navegación, ViewDidLoad es un buen lugar para crear el DVC.
    • inicializar valores en el árbol de control en ViewDidAppear. P.ej. puede agregar/eliminar/reemplazar elementos, secciones e incluso la raíz del DVC en este método. Pero no cree un nuevo DVC.
  6. No es un problema general con ViewControllers fugas cuando el usuario navega por la pila de navegación (me refiero al enlace de Bugzilla en el "Actualizar" en la pregunta). Esto también afecta a MT.D.Hay una solución bastante sencilla - añada la siguiente línea de código en ViewDidAppear del controlador de vista de los padres:

    // HACK: touch the ViewControllers array to refresh it (in case the user popped the nav stack) 
        // this is to work around a bug in monotouch (https://bugzilla.xamarin.com/show_bug.cgi?id=1889) 
        // where the UINavigationController leaks UIViewControllers when the user pops the nav stack 
        int count = this.NavigationController.ViewControllers.Length; 
    

Rolf hace un gran trabajo explicando por qué este error ocurre y por qué la solución funciona en el enlace de Bugzilla así que no lo repetiré

Espero que alguien encuentre esto útil. También espero que alguien más inteligente que yo tenga alguna guía sobre cómo manejar DidReceiveMemoryWarning y cómo dividir el trabajo entre ese método y ViewDidUnload.

actualización: un par de notas:

  • ahora se dan cuenta el protocolo para DidReceiveMemoryWarning y ViewDidUnload: el primero siempre se entrega a cada controlador de vista, mientras que el segundo sólo se envía a los controladores de vista de que no se muestran actualmente, Y no son más profundos que la raíz de la pila de navegación. Al final, decidí ignorar DidReceiveMemoryWarning porque realmente no tengo imágenes que guarde en la memoria caché y pueda volcar (según la guía de iOS). En ViewDidUnload, lanzo todos los recursos que asigné en ViewDidLoad.
  • Mi aplicación tiene un TabBar donde cada pestaña aloja un UINavigationController, la mayoría de las cuales empuja un DialogViewController. Un problema con el que estaba lidiando era filtrar DialogViewController después de que ViewDidUnload soltara la referencia al mismo. Intenté deshacerme del DVC en ViewDidUnload, pero iOS siguió queriendo reinvocarlo y recibí una excepción por invocar un selector en un objeto GC. Descubrí el motivo: el controlador de navegación sostenía el DVC en su matriz ViewControllers. La solución es liberar la matriz mediante la creación de una matriz de longitud cero en su lugar - en ViewDidUnload:

    this.ViewControllers = new UIViewController[0]; 
    

la matriz de edad ahora se GC'ed, y así será el DVC porque nada está apuntando a eso nunca más Y iOS no volverá a invocar el objeto. Nota: no es necesario llamar a Dispose en el DVC.

+6

Esa es una gran cantidad de información valiosa, gracias por poner todo junto. Solo una nota para lectores futuros: desde iOS 6, el sistema no llamará a 'ViewDidUnload'. –

Cuestiones relacionadas