2016-05-28 1 views
8

J'ai un problème sérieux avec mon application iOS.Les ViewControllers ne sont pas destructeurs

J'ai une logique de connexion dans mon application. Lors de la connexion et de la déconnexion, certains contrôleurs de vue ne sont pas destructeurs. Cela provoque certains problèmes, par exemple, certains événements que j'émets en utilisant NSNotifcationCenter sont émis plusieurs fois. Ces problèmes sont évitables, mais je veux vraiment une solution pour éviter que certains contrôleurs de vue restent ouverts en arrière-plan sans que je ne les contrôle.

La façon dont contrôler la logique de connexion est la suivante:

Dans le délégué de l'application fonction de démarrage, si l'utilisateur est déjà connecté, régler le régulateur de vue racine au contrôleur principal de vue utilisable. Par conséquent, je ne fais rien et le contrôleur de vue racine est défini sur le contrôleur de navigation du contrôleur de vue de connexion via le storyboard. Lorsque l'utilisateur se déconnecte, j'utilise une synchronisation modale pour ramener le contrôleur de vue sur le contrôleur de navigation du contrôleur de vue de connexion. Comme vous pouvez le comprendre, j'utilise des storyboards, swift et le tout dernier iOS.

Mon code est Segue que la déconnexion me prendre au LoginViewControler:

self.performSegueWithIdentifier("Logout", sender: self) 

Mon application Code délégué:

if (userDefaults.valueForKey("uid") != nil) { 
    let tabBarView = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("TabBarViewController") as! TabBarViewController 
    let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate 
    appDelegate.window?.rootViewController = tabBarView   
} 

Qu'est-ce que je fais mal?

Je vous serais reconnaissant de l'aide :)

EDIT

J'ai même essayé de mettre juste le contrôleur de vue racine dans l'action et fermeture de session n'a pas aidé non plus. Comment est-ce possible?

Voici comment je fais la fermeture de session maintenant:

let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate 
let newRootViewController = self.storyboard?.instantiateViewControllerWithIdentifier("LoginNavigationController") as! UINavigationController   

appDelegate.window!.rootViewController = newRootViewController 
+2

Lorsque vous vous déconnectez, quel est le contrôleur View racine de votre fenêtre? Vous ne le réinitialisez peut-être pas et la fenêtre maintient le CV en vie. –

+1

Comme ici est la fonctionnalité de connexion, vous devriez faire des demandes de backend. Pourriez-vous s'il vous plaît poster ce code à votre question? cela peut être un problème avec le nombre de retenue dans ce morceau de code. – Ramis

+3

J'ai peur qu'il n'y ait pas assez d'informations. C'est un problème avec les fuites de mémoire, elles peuvent être causées n'importe où. Il est très difficile de trouver une fuite de mémoire à partir de quelques morceaux de code. Le contrôleur peut s'empêcher de désallouer avec une fermeture, par exemple. – Sulthan

Répondre

1

Si le connecté écran présente la connexion écran et l'connexion écran présente l'connecté écran alors vous aurez un cycle qui continue à empiler sur de nouveaux contrôleurs de vue. Pour résoudre cela, il ne faut pas présenter l'autre, mais se détendre. Une autre possibilité est de tenir des instances de chacune comme singletons et de ne les présenter que.

+0

Je viens d'essayer d'utiliser dérouler segue pour passer de la vue principale à la vue de connexion, mais cela n'a pas résolu le problème. Toutes les idées, les singletons peuvent être une idée de ce que j'ai alors de mal à écrire du code pour nettoyer la classe, de quelle façon puis-je faire cela? – gal

+1

Si vous utilisez une séquence déroulante pour passer de la vue principale à la vue de connexion, il doit y avoir une instance de la vue de connexion et une nouvelle instance de la vue principale doit être créée à chaque fois. Assurez-vous que c'est le cas. Il est possible que vous ne vous détendiez pas correctement. À tout moment, il ne devrait pas y en avoir plus d'un de chaque. –

+0

dérouler n'aide pas dans ma situation parce que mon contrôleur de vue principale n'est pas sous un contrôleur de navigation – gal

1

Adam H. a raison. Si cela ne fonctionne pas, vérifiez les IBOutlets et les délégués qui ont des relations fortes et remplacez-les par des relations faibles. c'est-à-dire

@IBOutlet weak var collectionView: UICollectionView! 

Sans le mot clé faible, le contrôleur de vue ne sera jamais éliminé.

Selon la façon dont votre projet est configuré, si vous utilisez un contrôleur de navigation (que je recommande) chaque fois que quelqu'un se connecte à vous mettre

dispatch_async(dispatch_get_main_queue()) { 
    self.navigationController.popToRootViewControllerAnimated(true) 
} 

Ce sautera tout de la pile de navigation, qui sera disposer de tous les contrôleurs de vue (sauf si vous avez des relations fortes, alors ils ne seront pas éliminés)

+0

d'abord, je me suis assuré que tous les points de vente ont le mot-clé faible. Deuxièmement, je n'utilise pas le contrôleur de navigation pour cette transition. – gal

+0

Édité ma question – gal

+1

@IBOutlet est un faible par défaut. Peut-être existe-t-il d'autres propriétés qui devraient être faibles, mais ce ne sont pas des IBOutlets. –

2

Peu importe comment vous choisissez de gérer vos transitions, n'oubliez pas d'ajouter/supprimer l'observateur à chaque apparition du contrôleur de vue/disparaître.

+0

Je souhaite que certains événements fonctionnent toujours lorsque la vue n'apparaît pas. Donc, cela n'aide pas. – gal

1

J'ai implémenté quelque chose comme ça il n'y a pas si longtemps et pour moi, il semble que vous abusiez du cycle de vie de UINavigationController. Après avoir lu votre question deux fois, si je la comprends correctement, il semble que vous initialisiez votre contrôleur de vue de connexion en tant que UINavigationController qui empile les contrôleurs de vue. Une fois que l'utilisateur se déconnecte, vous gardez la pile, en ajoutant plus de ViewControllers à la pile en utilisant la fonction performSegue. Vous pouvez l'éviter en utilisant deux scènes différentes - 1) Login View Controller qui est autonome. 2) Flux principal de votre application - peut commencer avec UITabController/UINavigationController, les deux ou quoi que ce soit.

Dans AppDelegate, vous cochez - Si l'utilisateur est connecté - faites vos logiques et réglez l'application rootVC sur le flux principal vc. Sinon, vous définissez loginVC (UIViewController) comme racine. Cela vous permet également d'insérer le VC de connexion n'importe où dans le flux principal, si nécessaire, sans interférer avec le flux principal. Dans votre cas, loginVC est toujours la racine de UINavigationController, donc vous devez popToRootVC à chaque fois que vous voulez le voir ou effectuer une Ségrégation qui est pire parce que vous créez alors une autre instance d'UINavigationController et les ressources ne sont jamais désallouées. Il est évident qu'en programmation, dans la plupart des cas, il existe de nombreuses solutions à un problème. Je suis sûr que votre problème peut être résolu en utilisant votre flux. Je pense juste que c'est une mauvaise expérience pour empiler un loginVC sur un contrôleur de navigation.

+0

Down a voté pour quoi? Pas de commentaire pour le vote à la baisse. Ce genre d'actions décourage les gens de vous offrir de l'aide. Évidemment, le vote bas pour toutes les réponses - me fait me demander si c'est l'auteur de cette question ... De toute façon, je pense toujours que vous abusez de l'API d'Apple et vous devriez envisager de changer votre code. Bonne chance. – devdc

1

Une partie du problème est que la définition d'un nouveau rootViewController sur le UIWindow ne supprime pas la hiérarchie de vue de l'ancien contrôleur de vue racine. Cela laisse toutes sortes de références fortes en suspens, et si vous utilisez le débogage d'affichage de Xcode, vous pouvez voir que l'ancienne hiérarchie d'affichage est toujours là, derrière la nouvelle hiérarchie d'affichage de rootViewController.

Quelque chose comme cela devrait résoudre le problème pour vous et permettre à votre vue contrôleurs deinit:

let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate 
let newRootViewController = self.storyboard?.instantiateViewControllerWithIdentifier("LoginNavigationController") as! UINavigationController   

appDelegate.window??.rootViewController?.view.removeFromSuperview() 
appDelegate.window??.rootViewController?.dismissViewControllerAnimated(false, completion: nil) 
appDelegate.window??.rootViewController = newRootViewController  
+0

Hey, cette solution ne fonctionne pas:/ – gal

+1

@gal Désolé, cette solution supposait qu'il y avait une présentation modale qui se passe dans le contrôleur de vue racine en cours. J'ai mis à jour la réponse légèrement, et cela fonctionne pour moi dans divers cas similaires. Cela vous dérangerait-il de faire un essai? –

+0

Je l'ai essayé, je ne travaille toujours pas – gal

0

Vous devez retirer votre:

self.performSegueWithIdentifier("Logout", sender: self) 

Au lieu de cela, vous pouvez redéfinissant cette Segue méthode, cela suffit:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { 
     userDefaults.removeObjectForKey("uid") 
     if segue.identifier == "Logout" { 
      let newRootViewController = segue.destinationViewController 
      // newRootViewController is optional in case you want to pass vars 
      // do whatever you want with your newRootViewController 
     } 
} 

À propos de votre NSNotification il y a deux méthodes pour le supprimer:

NSNotificationCenter.defaultCenter().removeObserver(self, name: "NotificationIdentifier", object: nil) 
NSNotificationCenter.defaultCenter().removeObserver(self) // Remove from all notifications being observed 

Swift, vous pouvez mettre les removeObservers dans le deinitmethod nouvelle et spéciale.

enter image description here

+1

Cette procédure fonctionne bien sur un test de projet Xcode, s'il vous plaît motiver downvote –

1

se débarrasser du fichier ARC par fichier dans les paramètres de construction ou de gros par projet (il semble que vous pouvez avoir projet non ARC, mais ont des références faibles tout ce: non sportif je suppose mais vous pouvez avoir les deux).

Ensuite, remplacez conserver et relâchez dans le contrôleur de vue problématique et voyez qui détient la référence supplémentaire en rompant dans la priorité retenez et relâchez. Ce devrait être une expérience éducative. L'approche paresseuse consiste à tuer ARC uniquement pour le VC en question.

Je serais curieux de voir comment cela fonctionne pour VCS en rapide ;-)

moi pense que c'est encore une autre raison de rester dans un domicile objc alors plus jusqu'à ce que/si compilateur rapide et solidifie exécution (si jamais).

J'espère que cela aidera tout le monde. PS: Il faut une éternité pour compiler un fichier rapide dans mon projet et je n'ai pas d'idée quel fichier swift est à l'origine de ce problème. Duh.

1

Comme indiqué précédemment, il n'y a pas beaucoup d'informations pour fournir correctement la solution à votre question. Je peux vous suggérer de changer votre approche. J'ai fait un flux de travail similaire en utilisant un UINavigationController (NavigationController) lancé à partir AppDelegate, à l'intérieur si nous sommes connecté je mets comme ViewControllers:

(où auto est navigationController et RootViewController est un autre UINavigationController)

self.setViewControllers[loginViewController, rootViewController] 

Si vous n'êtes pas connecté vous mettez seulement loginViewController:

self.setViewControllers[loginViewController] 

dans ce cas vous pouvez mettre le rootViewController où l'utilisateur est connecté. Ceci est mon 2cent.

1

J'aime avoir une racine VC qui est juste vide. Lorsque l'application démarre, la racine VC affiche immédiatement login VC en tant que VC enfant de la racine VC. Lorsque l'utilisateur authentifie avec succès, le VC de connexion notifie la racine VC, qui ajoute principal VC en tant que fils de racine VC, transitions (avec une belle animation) de connexion VC à VC principal (en utilisant [self transitionFromViewController: toViewController: duration: options: animations: completion:]), puis supprime Connectez-vous VC comme un enfant et le rejette. Lors de la déconnexion, le VC principal notifie la racine VC qui fait ensuite la même chose à l'envers. Donc, la plupart du temps, vous n'avez que VC de connexion ou VC principal instancié; la seule fois où ils sont tous les deux instanciés est pendant la transition.

Je trouve que les segments sont utiles pour construire des prototypes rapides, mais pour les applications de production, je préfère ne pas les utiliser.

+1

Vous utilisez incorrectes downvotes. Vous devriez upvote réponses que vous trouvez utiles, et laisser le reste seul à moins qu'ils ne soient bâclés ou dangereux. http://stackoverflow.com/help/privileges/vote-down –

+0

Je ne peux que vous suggérer de faire la même chose que moi et de voter pour chaque réponse qui a été votée par ce mec. – devdc

1

Je suppose que la pile ViewControllers vous sera comme ceci: - 1er lancement: LoginVC - Après connexion: LoginVC - TabarVC - Cliquez sur Déconnexion: LoginVC - TabarVC - LoginVC ....

Alors votre code ci-dessous devrait fonctionner:

let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate 
let newRootViewController = self.storyboard?.instantiateViewControllerWithIdentifier("LoginNavigationController") as! UINavigationController   

appDelegate.window!.rootViewController = newRootViewController 

mais il ne :(à mon avis, vous devriez toujours laisser le tabarVC RootViewController et l'enregistrement TabarVC, si l'utilisateur ne loged-ou fermeture de session a appuyé sur, le présent loginVC.. et le rejeter au lieu de performSegue