Voilà un petit bout de temps que vous utilisez les contrôleurs, et vous avez certainement croisé des méthodes comme :
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
Et à moins d'avoir déjà été suffisamment curieux pour chercher par vous-même, vous ne savez sans doute pas précisément ce qu'il se passe dans cette méthode. Dans ce chapitre, nous allons lever un voile sur tout ça en parlant du cycle de vie du contrôleur.
Certes, il s'agit d'un chapitre purement théorique, mais ne paniquez pas ! Ça va vous servir à peu près tous les jours dans votre vie de développeur iOS, donc ça vaut le coup ! Et puis, ça ne vous fera pas de mal, je vous trouve un peu trop heureux là...
De quoi ça va causer ?
Non, on ne va pas parler de biologie ! Le cycle de vie du contrôleur décrit les étapes par lesquelles passe le contrôleur entre la demande d'affichage d'une nouvelle page et sa disparition de l'écran.
Autrement dit, le cycle de vie, c'est l'histoire de la naissance, la vie et la mort d'un contrôleur.
La naissance
Un contrôleur passe par de nombreuses étapes avant que la vue ne soit affichée à l'écran. Comprendre ces étapes va vous permettre de les utiliser pour effectuer certaines préparations ou actions sur le contrôleur.
Initialisation
La première étape, comme pour n'importe quel objet, c'est l'initialisation du contrôleur. L'objet contrôleur est créé en mémoire avec toutes ses propriétés. Comme toute initialisation, cela se fait à l'occasion de la méthode init
:
init?(coder aDecoder: NSCoder)
À cette étape, on a juste instancié un objet. Il n'est pas question de vue. C'est comme lorsque vous écrivez ceci :
var monObjet = Objet()
Chargement de la vue
L'étape suivante, c'est le chargement de la vue. À cette étape, on crée en mémoire la propriété view
du contrôleur, la vue principale (rappel sur cette notion ici). On va y ajouter toutes les sous-vues. On va pouvoir les placer, les modifier, etc. À cette étape, on prépare toutes les propriétés de la vue et de ses sous-vues. Mais on ne les affiche pas à l'écran.
Cela a lieu dans cette méthode :
func loadView()
Vous pouvez faire l'override de cette méthode si vous souhaitez créer vos vues dans le code. Mais si vous utilisez le storyboard, c'est fait automatiquement pour vous, et vous pouvez ignorer cette étape.
Une fois cette étape terminée, la fameuse fonction viewDidLoad
est appelée :
func viewDidLoad()
Quand cette fonction est appelée, la vue est donc complètement chargée en mémoire, mais elle n'est pas affichée à l'écran.
Affichage de la vue à l'écran
Le contrôleur est prêt, la vue est prête. On va pouvoir l'afficher à l'écran de l'iPhone.
Cette étape s'accompagne de deux méthodes :
func viewWillAppear(_ animated: Bool)
func viewDidAppear(_ animated: Bool)
La première est appelée juste avant que la vue soit affichée à l'écran. C'est le bon moment pour préparer une animation que l'on veut jouer à l'affichage de la vue.
La deuxième est appelée juste après que la vue soit affichée à l'écran. C'est le bon moment pour lancer une animation, un son ou une vidéo. La vue vient d'arriver à l'écran, l'utilisateur va pouvoir en profiter !
La vie
Ça y est ! La vue est à l'écran. L'utilisateur va interagir avec en cliquant sur des boutons, en la visualisant simplement, ou en faisant des gestes sur l'écran tactile.
C'est là qu'intervient la majeure partie de votre travail en tant que développeur. Il y a simplement une petite chose à noter ici : la gestion de la mémoire.
Vous avez peut-être aperçu cette méthode :
func didReceiveMemoryWarning()
Un iPhone, contrairement à un ordinateur, c'est petit. Du coup, ses capacités sont limitées. Il ne peut retenir qu'un nombre limité d'objets en même temps dans ce qu'on appelle sa mémoire vive. Et s’il est sur le point d'atteindre sa limite, il vous prévient avant, pour que vous puissiez libérer la mémoire de choses inutiles.
Prenons un exemple : si vous voulez afficher le texte complet de toute la saga Harry Potter dans une seule vue, la mémoire de votre iPhone risque de flancher, et vous aurez l'occasion de supprimer une partie du texte dans la fonction didReceiveMemoryWarning pour alléger la charge.
La mort
Eh oui, cette histoire ne se finit pas très bien... :(
Disparition de l'écran
Lorsque votre vue va disparaître de l'écran, si vous passez à la vue suivante par exemple, deux méthodes vont être appelées :
func viewWillDisappear(_ animated: Bool)
func viewDidDisappear(_ animated: Bool)
Comme précédemment, la première est appelée avant la disparition de la vue à l'écran, et la deuxième juste après.
Suppression du contrôleur
Si votre vue n'est plus à l'écran, cela ne signifie pas forcément la fin définitive pour votre contrôleur. Prenons un exemple pour bien comprendre ce point.
Mettons que j'aie un Navigation Controller qui gère la navigation entre deux contrôleurs A et B.
1/ À l'allumage de l'application, le contrôleur A est créé, puis la vue A correspondante est chargée en mémoire, et ensuite la vue A est montrée à l'écran.
2/ Ensuite, l'utilisateur navigue vers le contrôleur B. La vue A du coup disparaît de l'écran, et le contrôleur B est créé, puis la vue B est chargée et enfin elle apparaît à l'écran.
Notre pile de navigation ressemble à ceci :
B |
A |
Donc le contrôleur A existe toujours puisqu'il est stocké dans la pile de navigation. Mieux, la vue est toujours chargée ! Elle n'est juste pas montrée à l'écran.
3/ L'utilisateur revient à la vue A, et la pile de navigation ressemble à ceci :
A |
Non seulement la vue B a disparu de l'écran, mais cette fois le contrôleur B n'est plus stocké dans la pile de navigation. Le contrôleur B a bel et bien été supprimé de la mémoire.
La mort effective du contrôleur a donc lieu lorsqu'il quitte la pile de navigation.
viewDidLoad VS viewDidAppear
Cela a une conséquence importante pour deux méthodes que vous risquez d'utiliser souvent, viewDidLoad
et viewWillAppear
:
viewDidLoad est appelée une seule fois dans la vie du contrôleur ;
viewDidAppear est appelée plusieurs fois.
Pourquoi ?
Reprenons notre exemple avec les contrôleurs A et B. Et concentrons-nous uniquement sur l'enchaînement des méthodes du cycle de la vie de A.
1/ A est appelée à l'écran, les méthodes suivantes sont exécutées :
// Le contrôleur est créé en mémoire
init?(coder aDecoder: NSCoder)
// La vue est chargée
loadView()
viewDidLoad()
// La vue apparaît à l'écran
viewWillAppear(_ animated: Bool)
viewDidAppear(_ animated: Bool)
Jusque-là, viewDidLoad
et viewDidAppear
ont toutes les deux été appelées une fois.
2/ On passe à la vue B. Sur le contrôleur A, les méthodes suivantes sont exécutées.
// La vue disparaît de l'écran
viewWillDisappear(_ animated: Bool)
viewDidDisappear(_ animated: Bool)
3/ On revient à la vue A. Les méthodes suivantes sont appelées :
// La vue revient à l'écran
viewWillAppear(_ animated: Bool)
viewDidAppear(_ animated: Bool)
Et voilà, viewDidAppear
(et viewWillAppear
, d'ailleurs) est appelée une seconde fois, mais viewDidLoad
n'a pas été appelée.
Pourquoi on ne repasse pas par viewDidLoad
?
Vous connaissez déjà la réponse ! Alors, débrouillez-vous !
Si je pose la question, à priori...
Bon OK. Comme on l'a vu juste au-dessus, le contrôleur A reste stocké dans la pile de navigation quand B est à l'écran. Donc le contrôleur A continue d’exister, et il garde la vue chargée en mémoire pour pouvoir à tout moment la refaire passer à l'écran sans avoir à tout recharger.
Et donc si je fais dix fois l'aller-retour entre A et B, je vais passer dix fois par viewDidAppear,
mais je ne repasse plus par viewDidLoad
car la vue n'a pas été rechargée.
OK je vois.
Merci !
Du coup, si vous voulez recharger le contenu d'une page à chaque fois qu'elle apparaît, il faudra mettre cela dans viewWillAppear
plutôt que viewDidLoad
. En revanche, si vous souhaitez vous placer en tant qu'observateur, pour une notification par exemple, vous n'avez besoin de le faire qu'une fois, donc faites-le dans viewDidLoad
.
En résumé
En guise de résumé, je vous propose le schéma ci-dessous :
Ça y est ! Vous avez toutes les bases de navigation, et comme pour tout sur iOS, il y a beaucoup d’autres choses que vous pouvez aller découvrir par vous-même.
Dans la prochaine partie, nous allons créer le formulaire d’inscription, et vous allez découvrir comment utiliser les composants principaux d’un formulaire, comme les switchs, les champs de texte, ou encore les sélecteurs.