• 10 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 05/04/2023

Bonus : Découvrez le concept de références

Hop hop hop, tu ne voulais pas nous parler d'un truc important encore ?!

Ah si ! Quand je vous ai présenté le protocole  UITableViewDataSource  , on a d'une part limité ce protocole à des classes Objective-C, en adossant  NSObjectProtocol  à la déclaration de notre protocole :

protocol UITableViewDataSource: NSObjectProtocol {

   // (...)

}

Et dans notre TableView, je vous ai dit que l'on avait notre objet  dataSource  , mais avec le mot réservé  weak  :

class UITableView: UIScrollView {

   weak var dataSource: UITableViewDataSource?

}

Alors, pourquoi a-t-on fait tout cela ? Qu'est-ce que cela veut dire ? On va rapidement toucher au monde des références pour comprendre tout cela.

Les références

Dans les langages de programmation un peu modernes (comme Swift !), les objets que vous créez restent dans la mémoire, tant qu'au moins une référence existe vers cet objet.

Quand mon objet n'a plus aucun autre objet ayant une référence sur lui, pouf ! Il disparaît ! Sous iOS, la technologie qui fait tout cela s'appelle Automatic Reference Counting (ARC).

Dans d'autres langages, on parle de Garbage Collector pour désigner cette technologie : le programme nettoie la mémoire en enlevant les objets qui ne servent plus à rien. Et comment sait-on qu'un objet ne sert plus à rien ? Quand il n'a plus de références ! Dans ce cas, mon programme n'a plus aucun moyen d'accéder à cet objet, et celui-ci est considéré comme perdu.

Tout cela ne s'applique qu'aux classes ; les structures et les énums ne sont pas concernées. 

Euh… mais pourquoi ?

Tout simplement parce que c’est comme ça qu'est conçu le Swift. Les classes sont naturellement passées par référence, et les structures et les énumérations sont passées par copie.

Par copie ? 😜

Oui ! À chaque fois qu’une variable de type structure ou énumération est passée d’un objet à un autre, il s’agit en fait d’une copie de cet objet qui est faite en mémoire, et non du même objet réellement !

L’utilisation du protocole  NSObjectProtocol  garantit que l’objet qui implémente le protocole sera une classe, car les classes Objective-C ne sont compatibles qu’avec des classes Swift. Il n’existe pas de structure ou d'énumération en Objective-C.

Sachez que vous pouvez aussi définir la conformance à une classe directement avec le mot clé  class  dans la déclaration de notre protocole. L’effet sera identique, si ce n’est que ça n’oblige pas d’avoir une classe Objective-C derrière.

Un schéma fleché :  Mon application (flèche)NavigationController (flèche)ViewController (double flèche allant soit à UiTableView, soit à UIButton)
Les flèches en noir représentent des références.

Ici, ma TableView et mon bouton restent bien dans la mémoire, puisque j'ai au moins un objet qui les référence, mon View Controller. Et celui-ci reste aussi dans la mémoire, puisqu'il a également une référence... Et ainsi de suite.

OK, mais tout cela ne me dit pas ce qu'est une référence !

Une référence, c'est très simple ! Quand j'écris ceci :

class ListViewController: UIViewController {

   var tableView: UITableView

}

… je crée une référence de mon  ListViewController  vers ma propriété  tableView  . Tant que mon contrôleur est dans la mémoire (et généralement, il y reste tant qu'il est présent dans ma navigation), ma TableView est là aussi. C'est bien rassurant, finalement !

Le mot-clé weak

Si on reprend notre exemple de delegate de tout à l'heure, ma TableView s'écrit comme cela, si j'enlève ce fameux mot  weak  :

class UITableView: UIScrollView {

   var dataSource: UITableViewDataSource?

}

Donc en termes de référence, quand j'écris après dans mon View Controller  tableView.dataSource = self  , cela donne ça :

Propriété tableview View Controller =><= UITableView  Propriété dataSource
ViewController et UITableView

Et là… c'est le drame !

Pourquoi c’est le drame ? 😰

Parce que, sans faire attention, j'ai créé un retain cycle. En fait, chaque objet a une référence vers l'autre. Même si mon View Controller n'est plus dans la navigation, et qu'aucun objet n'a de référence vers lui, le couple View Controller <> TableView ne disparaîtra jamais, car chacun a au moins une référence, celle de l'autre objet du couple. Cela crée finalement une fuite mémoire.

Mais que va-t-on bien pouvoir faire ??! 🥺

Pas de panique ! Vous l'aurez sans doute compris, c'est là que le mot  weak  entre en jeu ! Pour comprendre  weak  , on va d'abord regarder ce que fait son contraire :  strong  .

Par défaut, quand je déclare une propriété sur un objet de type  Objet  :

var monObjet: Objet

Cela équivaut à écrire :

strong var monObjet: Objet

Ma référence vers mon instance de  Objet  doit être forte pour maintenir mon objet dans la mémoire. Quand le nombre de références fortes vers mon objet tombe à zéro, il n'y a plus rien pour le garder dans la mémoire, et c'est là qu'il disparaît.

Donc quand je prépose weak à la déclaration de ma variable, j'indique que je veux une référence faible. Et au contraire d'une référence forte, une référence faible ne retient pas mon objet dans la mémoire ! Je peux accéder à mon objet dans la mémoire, mais ce n'est pas moi qui le retiendrai. Je ne vais pas le retenir s'il doit disparaître.

Autrement dit, lorsque ARC compte les références pour savoir si un objet doit être supprimé de la mémoire, il ne compte que les références fortes, les faibles ne comptant pas.

Si on reprend notre schéma de tout à l'heure, avec la déclaration en  weak  , cela donne ça :

Propriété tableView ViewController=>UITableView Propriété weak dataSource
Voilà qui est mieux

Et voilà le travail, le mot weak permet de briser ce fameux retain cycle ! Quand mon contrôleur ne sera plus dans la navigation, et qu'il n'aura plus de référence vers lui, ma  tableView  ne l'empêchera pas d'être enlevé de la mémoire.

Retour sur les outlets

Ouais, mais on ne me la fait pas. Ton schéma est faux, on a déclaré notre TableView avec un IBOutlet et avec le mot clé  weak  .

C'est vrai. Quand on déclare un outlet avec  weak  , en théorie, notre objet ne devrait même pas pouvoir rester dans la mémoire, puisqu'il n'y a aucune référence  strong  pour le retenir, non ?

Bien sûr, c'est faux, sinon nos apps ne fonctionneraient pas depuis le début, hé hé !

Alors, que se passe-t-il au juste en réalité ? Eh bien, il suffit de se souvenir que tout bon  UIViewController  gère une vue principale. Si on reprend notre schéma, dans la réalité, on a ceci :

À gauche, Le ViewControler. Une flèche
Gestion de l'UIView et de l'UITableView

La vue principale maintient un lien fort sur l'ensemble de ses sous-vues via la propriété  subviews  . Et tant que mon contrôleur est présent, il maintient aussi un lien fort sur sa vue principale via la propriété  view  . Donc ma  tableView  reste bien dans la mémoire.

On déclare donc nos outlets en weak pour éviter une redondance, ou pour éviter des problèmes si on crée des liens entre objets qui n'ont rien à voir ! Depuis le temps que l'on déclare des outlets, cela devait vous démanger de ne pas savoir, non ? 

Si tout cela vous paraît compliqué, pas de panique. Retenez simplement le concept de retain cycle, et que si deux objets s'autoréférencent, vous allez avoir des problèmes de mémoire. Lorsque vous créerez vos propres delegates, pensez à les indiquer en  weak  pour éviter ce problème, et tout ira bien !

En résumé

  • Un objet reste dans la mémoire, tant qu'au moins une référence forte existe vers cet objet.

  • Si deux objets s'autoréférencent avec des références fortes, cela crée un retain cycle : mes objets ne peuvent plus être nettoyés de la mémoire.

  • Le mot réservé weak permet de résoudre ce problème : les références dites faibles ne comptent pas lorsque le programme détermine si l'objet est encore utile ou pas.

  • On déclare un delegate en  weak  pour éviter de créer un cycle de rétention et donc une fuite mémoire.

  • Les propriétés avec des  IBOutlets  sont déclarées en  weak  : en général, l'objet aura déjà une référence forte interne, et en rajouter une autre ne servira pas.

Prêt à passer à l'action ? Dans le chapitre suivant, vous allez vous entraîner à réaliser une application… listant les 50 meilleurs films du monde !  :soleil:

Exemple de certificat de réussite
Exemple de certificat de réussite