2012-06-12 13 views
19

Ho un'app MonoTouch con UITabBarController, con ciascuna delle schede è un UINavigationController. Alcuni di questi avvolgono un UIViewController che aggiunge UITableView e una UIToolbar, mentre altri racchiudono un DialogViewController.Gestione memoria/risorse utilizzando MonoTouch e MonoTouch.Dialog

Non ho prestato molta attenzione alla gestione della memoria/visualizzazione fino ad ora (sono stato principalmente in esecuzione nel simulatore), ma come ho iniziato a testare su un dispositivo reale, ho notato alcuni guasti dovuti a condizioni di memoria insufficiente (ad esempio, l'app viene terminata e scopro dal mio registro che DidReceiveMemoryWarning è stato chiamato prima). Altre volte noto pause prolungate nella reattività dell'app che presumo siano dovute a un ciclo GC.

Finora ho assunto che ogni DialogViewController che inserisco nello stack nav pulirà le sue viste e altre cose verranno allocate quando lo popò. Ma sto iniziando a rendermi conto che probabilmente non è così facile, e che ho bisogno di iniziare a chiamare Dispose() sulle cose.

Esistono best practice su come gestire la gestione delle risorse e della memoria con MonoTouch e MT.D? In particolare:

  • È necessario chiamare Dispose su un DialogViewController dopo che è spuntato? Se sì, dove è meglio farlo? (ViewDidUnload? DidReceiveMemoryWarning? Destructor?)
  • Il DVC dispone automaticamente di oggetti come il RootElement che gli viene passato o devo preoccuparmi di questo? Che ne dici di UIImages che carica come parte del rendering di una cella di una tabella (ad esempio StyledStringElement)?
  • Ci sono dei luoghi in cui dovrei chiamare GC.Collect() per ottimizzare le raccolte in modo da non perdere un po 'di reattività quando si verifica un GC?
  • Il garbage collector generazionale aiuta con i problemi di interattività ed è abbastanza stabile da poter essere utilizzato in un'app di produzione? (Credo sia ancora pubblicizzato come "sperimentale" in MonoDevelop 3.0.2/MT 4.3.3)
  • Cosa devo fare in DidReceiveMemoryWarning per ridurre la probabilità che iOS spari la mia app? Dal momento che ogni controller di visualizzazione non visibile sembra ricevere questa chiamata, suppongo che dovrei pulire le risorse del controller di visualizzazione ... dovrei fare lo stesso tipo di cose che faccio in ViewDidUnload?
  • Non riesco a richiamare il mio ViewDidUnload (anche dopo aver ricevuto DidReceiveMemoryWarning). In effetti non ricordo di averlo mai visto nel mio log. Se iOS ha sempre chiamato il mio ViewDidUnload dopo DidReceiveMemoryWarning, ho potuto semplicemente fare tutto il cleanup in ViewDidUnload ... Qual è il modo migliore per dividere la responsabilità di cleanup tra ViewDidUnload e DidReceiveMemoryWarning?

Mi scuso per il carattere generale di questa domanda - questo mi sembra un buon argomento per un whitepaper, ma non ho trovato alcuna ...

Aggiornamento: per rendere la questione più concreta : dopo aver usato Instruments e il profiler Xamarin Heapshot, mi è chiaro che sto perdendo UIViewControllers quando l'utente apre lo stack di navigazione. Rolf ha depositato un bug per questo e ha due dups, quindi questo è un vero problema per più di me. Sfortunatamente non ho trovato una buona soluzione per i controllori di perdite UIViewControllers - Non ho trovato un buon posto per chiamare Dispose() su di essi. Il posto naturale per liberare risorse allocate da ViewDidLoad è nel messaggio ViewDidUnload, ma non viene mai chiamato sul simulatore, quindi il mio footprint di memoria continua a crescere.Sul dispositivo, vedo DidReceiveMemoryWarning, ma sono riluttante a utilizzare questo come spazio per liberare il mio viewcontroller e le sue risorse poiché non sono sicuro che iOS scaricherà effettivamente la mia vista, e quindi non è garantito che il mio ViewDidLoad venga chiamato di nuovo o (portando ad un ViewDidAppear che avrebbe bisogno di codificare in modo difensivo contro le situazioni in cui le sue risorse sottostanti sono state smaltite). Mi piacerebbe avere qualche consiglio su come uscire da questo casino ...

risposta

30

Ho passato un paio di giorni nel codice sorgente MT.D e nel profiler. Mentre sto ancora cercando un orientamento generale su ciò che il miglior modello di progettazione per la realizzazione è DidReceiveMemoryWarning e ViewDidUnload, ho alcune osservazioni di carattere generale da condividere che potrebbe essere utile per qualcuno:

  1. MonoTouch.Dialog è molto ben comportata. Non perde alcuna risorsa durante l'uso normale. Mantiene una struttura di controllo in DVC.Root e il metodo Dispose di ogni elemento disattiva il controllo UIKit sottostante. Non devi nemmeno preoccuparti di smaltire un vecchio RootElement se hai sostituito DVC.Root - il setter della proprietà lo elimina automaticamente. Nel complesso, MT.D non sembra soffrire di problemi di memoria significativi. C'è un'eccezione: vedi sotto.
  2. Quando si creano i propri elementi personalizzati (ad esempio MultilineEntryElement), assicurarsi di sovrascrivere il metodo Dispose (bool), disporre il controllo UIKit sottostante (ad esempio UITextView) e concatenare il metodo Dispose() della classe base. Il codice sorgente nel progetto github di Miguel su MT.D offre molti buoni esempi. Tutti gli elementi implementano il modello Dispose standard (anche se omettono un distruttore/finalizzatore che chiama Dispose (false)).
  3. Quando si implementano i controller di vista personalizzati, in genere non è necessario implementare le sottoclassi Dispose su UIViewController, né sulle classi DataSource o Delegate TableView. Quando il controller della vista riceve GC, chiamerà correttamente Dispose sui suoi riferimenti. Tutte le celle allocate in DataSource verranno correttamente disposte.
  4. Come eccezione a (3) - Ho riscontrato un brutto problema quando aggiungevo la mia sottoview alla cella di un TableView. Questa sottoview è un controllo che ho creato chiamato "UICheckbox" che alla fine eredita da UIImageView, che ha due UIImages (on e off) e un evento pubblico chiamato Clicked. Ho riscontrato un problema solo quando un gestore di eventi che fa riferimento ai membri di DataSource è agganciato a questo evento (se il gestore di eventi non fa riferimento a DataSource o al controller stesso, va tutto bene). Tuttavia, quando vengono soddisfatte le condizioni di cui sopra, e il controller viene ignorato, apparentemente c'è un ciclo che il GC non riesce a capire, e ogni UICheckbox che ho messo sul TableView è trapelato (insieme alle sue immagini). L'unico modo in cui ho trovato di aggirare questo era di aggiungere il codice a ViewDidDisappear per disporre del ViewController e pulire il suo stato IFF non è più in qualsiasi punto dello stack di navigazione. È hacky ma funziona.
  5. In generale, io aderisco nel seguente schema per l'assegnazione degli oggetti nei miei controller di vista:

    • destinare nulla nel costruttore (usarlo solo per passare stato in)
    • creare una struttura di controllo in viewDidLoad (e smaltirlo in ViewDidUnload). pensa "InitializeComponent" in XAML (se questo aiuta). Se l'UIViewController sta per spingere un DialogViewController nello stack nav, ViewDidLoad è un buon posto per creare il DVC.
    • Inizializza i valori nell'albero di controllo in ViewDidAppear. Per esempio. In questo metodo puoi aggiungere/eliminare/sostituire elementi, sezioni e anche il root del DVC. Ma non creare un nuovo DVC.
  6. C'è un problema generale con la perdita di ViewControllers quando l'utente naviga nello stack nav (faccio riferimento al collegamento bugzilla nell'Aggiornamento nella domanda). Questo riguarda anche l'MT.D.C'è una soluzione abbastanza semplice - aggiungere la seguente riga di codice nel viewDidAppear del controller vista padre:

    // 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 fa un grande lavoro che spiega il motivo per cui questo bug accade e perché il soluzione funziona nel link bugzilla , quindi non lo ripeterò.

Spero che qualcuno lo trovi utile. Spero anche che qualcuno più intelligente di me abbia delle indicazioni su come gestire DidReceiveMemoryWarning e su come suddividere il lavoro tra quel metodo e ViewDidUnload.

Aggiornamento: un paio di note:

  • ora mi rendo conto il protocollo per DidReceiveMemoryWarning e ViewDidUnload: il primo è sempre consegnato ad ogni controller della vista, mentre il secondo viene inviato solo per i controller di vista che non sono attualmente visualizzati, E non sono più profondi della radice dello stack di navigazione. Alla fine, ho deciso di ignorare DidReceiveMemoryWarning perché non ho davvero immagini che memorizzo nella cache e posso scaricare (come da guida iOS). In ViewDidUnload, rilascio tutte le risorse allocate in ViewDidLoad.
  • La mia app ha un TabBar in cui ogni scheda ospita un UINavigationController, la maggior parte dei quali spinge un DialogViewController. Un problema che stavo affrontando stava perdendo il DialogViewController dopo che ViewDidUnload ha lasciato andare il riferimento ad esso. Ho provato a Smaltire il DVC in ViewDidUnload, ma iOS continuava a voler reinvocarlo e stavo ottenendo un'eccezione per invocare un selettore su un oggetto GC. Ho scoperto il motivo: il controller di navigazione stava trattenendo il DVC nel suo array ViewControllers. La soluzione è quella di rilasciare la matrice creando una matrice di lunghezza zero al suo posto - in ViewDidUnload:

    this.ViewControllers = new UIViewController[0]; 
    

la vecchia matrice verrà ora GC'ed, e così il DVC perché nulla punta ad esso più. E iOS non rinvierà mai l'oggetto. Nota: non è necessario chiamare Dispose sul DVC.

+6

Questo è un sacco di informazioni preziose, grazie per mettere tutto insieme. Solo una nota per i futuri lettori: da iOS 6, il sistema non chiamerà 'ViewDidUnload'. –

Problemi correlati