Note: j'ai créé un projet simple-vous pouvez voir comment le fait de passer entre les types de
UIButton
etCustomButton
dans le storyboard changements de comportement du GC.Je vais essayer d'obtenir ma tête enroulée autour de MonoTouch garbage collector.
Le problème est similaire à celui qui est fixé dans MT 4.0, cependant avec un héritage de types.Pour illustrer cela, considérons deux contrôleurs de vue, le parent et l'enfant.
Point de vue d'enfant contient un seul
UIButton
qui écrit dans la console sur le robinet.
ContrôleurDispose
méthode lève une exception si c'est difficile à manquer.Va ici de l'enfant-vue-contrôleur:
public override void ViewDidLoad () { base.ViewDidLoad (); sayHiButton.TouchUpInside += (sender, e) => SayHi(); } } void SayHi() { Console.WriteLine("Hi"); } protected override void Dispose (bool disposing) { throw new Exception("Hey! I've just been collected."); base.Dispose (disposing); }
Parent-vue-contrôleur juste présente contrôleur enfant et définit une horloge de le rejeter et de les exécuter GC:
public override void ViewDidLoad () { base.ViewDidLoad (); var child = (ChildViewController)Storyboard.InstantiateViewController("ChildViewController"); NSTimer.CreateScheduledTimer(2, () => { DismissViewController(false, null); GC.Collect(); }); PresentViewController(child, false, null); }
Si vous exécutez ce code, il se bloque de façon prévisible à l'intérieur d'
ChildViewController.Dispose()
appelé à partir de son finaliseur parce que contrôleur enfant a été nettoyée. Cool.Maintenant, ouvrez le storyboard et le bouton de changement de type d'
CustomButton
. MonoDevelop va générer un simpleUIButton
sous-classe:[Register ("CustomButton")] public partial class CustomButton : UIButton { public CoolButton (IntPtr handle) : base (handle) { } void ReleaseDesignerOutlets() { } }
En quelque sorte modifier le type de bouton
CustomButton
est suffisant pour tromper garbage collector dans la pensée de l'enfant contrôleur n'est pas encore admissible à la collection.Comment est-ce possible?
Réponse
Trop de publicités?C'est un malheureux effet secondaire de MonoTouch (qui est le garbage collector) d'avoir à vivre dans une référence compté monde (ObjectiveC).
Il y a quelques éléments d'information nécessaires pour être en mesure de comprendre ce qu'il se passe:
- Pour chaque objet géré (dérivé de NSObject), il existe un objet natif.
- Pour géré personnalisé classes (issus de classes du framework tels que UIButton ou UIView), l'objet géré doit rester en vie jusqu'à ce que le natif de l'objet est libéré [1]. La façon dont cela fonctionne est que lorsqu'un objet natif a un compteur de référence de 1, nous n'empêchent pas l'instance gérée à partir de l'obtention de ces ordures. Dès que le nombre de références augmente au-dessus de 1, nous empêcher de l'instance gérée à partir de l'obtention de ces ordures.
Ce qui se passe dans votre cas est un cycle, qui traverse la MonoTouch/ObjectiveC pont et en raison des règles ci-dessus, le GC ne peut pas déterminer que le cycle peuvent être collectées.
C'est ce qui se passe:
- Votre ChildViewController a un sayHiButton. Le natif ChildViewController faudra retenir de ce bouton, de sorte que son nombre de références sera de 2 (une référence détenues par la gestion de CustomButton exemple + une référence tenue par le natif ChildViewController).
- Le TouchUpInside gestionnaire d'événement est une référence à la ChildViewController instance.
Vous voyez maintenant que la CustomButton l'instance ne sera pas libéré, parce que son compte de référence est de 2. Et le ChildViewController l'instance ne sera pas libéré parce que le CustomButton du gestionnaire d'événement est une référence.
Il ya un couple de façons de briser le cycle de résoudre ce problème:
- Détacher le gestionnaire d'événements lorsque vous n'en avez plus besoin.
- Disposer la ChildViewController lorsque vous n'en avez plus besoin.
[1] C'est parce qu'un objet géré peut contenir de l'état utilisateur. Pour les objets gérés qui sont un miroir d'un natif correspondant de l'objet (comme la gestion de UIView exemple) MonoTouch sait que l'instance ne peut contenir de l'état, de sorte que dès que aucun code managé a une référence à l'instance gérée, le GC peut le recueillir. Si une instance gérée est nécessaire à un stade ultérieur, nous venons de créer un nouveau.