Swift From Scratch Initialisation et délégation d'initialiseur

Dans la leçon précédente de Swift From Scratch, nous avons créé une application de tâches fonctionnelle. Le modèle de données pourrait utiliser un peu d'amour, cependant. Dans cette dernière leçon, nous allons refactoriser le modèle de données en implémentant une classe de modèle personnalisée..

1. Le modèle de données

Le modèle de données que nous allons implémenter comprend deux classes, une Tâche classe et un Faire classe qui hérite de la Tâche classe. Pendant que nous créons et implémentons ces classes de modèle, nous continuons notre exploration de la programmation orientée objet dans Swift. Dans cette leçon, nous allons zoomer sur l'initialisation d'instances de classe et sur le rôle joué par l'héritage lors de l'initialisation..

le Tâche Classe

Commençons par la mise en œuvre de la Tâche classe. Créez un nouveau fichier Swift en sélectionnant Nouveau> Fichier… de Xcode Fichier menu. Choisir Fichier rapide du iOS> Source section. Nommez le fichier Task.swift et frapper Créer.

L'implémentation de base est courte et simple. le Tâche la classe hérite de NSObject, défini dans le Fondation cadre, et a une propriété variable prénom de type Chaîne. La classe définit deux initialiseurs, init () et init (nom :). Il y a quelques détails qui pourraient vous faire trébucher, alors laissez-moi vous expliquer ce qui se passe.

classe Foundation import tâche: NSObject nom de la variable: substitution de la commodité de la chaîne init () self.init (nom: "Nouvelle tâche") init (nom: Chaîne) self.name = nom

Parce que le init () méthode est également définie dans le NSObject classe, nous devons préfixer l'initialiseur avec le passer outre mot-clé. Nous avons abordé les méthodes principales plus tôt dans cette série. dans le init () méthode, nous invoquons la init (nom :) méthode, passage "Nouvelle tâche" comme valeur pour le prénom paramètre.

le init (nom :) méthode est un autre initialiseur, acceptant un seul paramètre prénom de type Chaîne. Dans cet initialiseur, la valeur du prénom paramètre est attribué à la prénom propriété. C'est assez facile à comprendre. Droite?

Initialisateurs désignés et pratiques

Ce qui est avec le commodité mot-clé préfixant le init () méthode? Les classes peuvent avoir deux types d'initialiseurs, désigné initialisateurs et commodité initialiseurs. Les initialiseurs de commodité sont précédés du préfixe commodité mot-clé, ce qui implique que init (nom :) est un initialiseur désigné. Pourquoi donc? Quelle est la différence entre les initialiseurs désignés et pratiques??

Initialisateurs désignés initialiser complètement une instance d'une classe, ce qui signifie que chaque propriété de l'instance a une valeur initiale après l'initialisation. En regardant le Tâche classe, par exemple, on voit que le prénom propriété est définie avec la valeur de la prénom paramètre de la init (nom :) initialiseur. Le résultat après l'initialisation est une initialisation complète Tâche exemple.

Initialisateurs de commodité, cependant, vous vous fiez à un initialiseur désigné pour créer une instance totalement initialisée de la classe. C'est pourquoi le init () initialiseur du Tâche la classe invoque le init (nom :) initialiseur dans sa mise en œuvre. Ceci est appelé délégation d'initialiseur. le init () l’initialisateur délègue l’initialisation à un initialiseur désigné pour créer une instance complètement initialisée du Tâche classe.

Les initialiseurs de commodité sont facultatifs. Toutes les classes ne disposent pas d'un initialiseur de commodité. Des initialiseurs désignés sont requis et une classe doit avoir au moins un initialiseur désigné pour créer une instance entièrement initialisée.

le NSCoding Protocole

La mise en œuvre de la Tâche la classe n'est pas terminée, cependant. Plus tard dans cette leçon, nous écrirons un tableau de Faire instances sur le disque. Ceci n’est possible que si des instances du Faire la classe peut être encodée et décodée.

Ne vous inquiétez pas, ce n'est pas sorcier. Nous avons seulement besoin de faire la Tâche et Faire les classes sont conformes à la NSCoding protocole. C'est pourquoi le Tâche la classe hérite de la NSObject classe depuis le NSCoding protocole ne peut être implémenté que par des classes héritant directement ou indirectement de NSObject. Comme le NSObject classe, la NSCoding le protocole est défini dans le Fondation cadre.

L'adoption d'un protocole est quelque chose que nous avons déjà couvert dans cette série, mais il y a quelques pièges que je veux signaler. Commençons par dire au compilateur que le Tâche classe conforme à la NSCoding protocole.

classe Foundation import tâche: NSObject, NSCoding nom var: String…

Ensuite, nous devons implémenter les deux méthodes déclarées dans NSCoding protocole, init? (codeur :) et encoder (avec :). La mise en œuvre est simple si vous connaissez le NSCoding protocole.

classe d'importation import Foundation Task: NSObject, NSCoding nom_variable: chaîne @objc obligatoire init? (codeur aDecoder: NSCoder) nom = aDecoder.decodeObject (forKey: "nom") comme! Chaîne @objc func encode (avec aCoder: NSCoder) aCoder.encode (nom, forKey: "nom") remplacement prioritaire init () self.init (nom: "Nouvelle tâche") init (nom: Chaîne) self.name = name

le init? (codeur :) initializer est un initializer désigné qui initialise un Tâche exemple. Même si nous mettons en œuvre le init? (codeur :) méthode pour se conformer à la NSCoding protocole, vous n’aurez jamais besoin d’appeler cette méthode directement. La même chose est vraie pour encoder (avec :), qui code une instance du Tâche classe.

le Champs obligatoires mot-clé préfixant le init? (codeur :) méthode indique que chaque sous-classe du Tâche la classe doit implémenter cette méthode. le Champs obligatoires mot clé s'applique uniquement aux initialiseurs, c'est pourquoi nous n'avons pas besoin de l'ajouter à la encoder (avec :) méthode.

Avant de continuer, nous devons parler de la @objc attribut. Parce que le NSCoding protocole est un protocole Objective-C, conformité du protocole ne peut être vérifié qu'en ajoutant le @objc attribut. Dans Swift, la conformité de protocole et les méthodes de protocole facultatives n’existent pas. En d’autres termes, si une classe adhère à un protocole particulier, le compilateur vérifie et attend que chaque méthode du protocole soit implémentée..

le Faire Classe

Avec le Tâche classe mise en œuvre, il est temps de mettre en œuvre le Faire classe. Créez un nouveau fichier Swift et nommez-le ToDo.swift. Regardons la mise en œuvre de la Faire classe.

classe Foundation à faire: tâche var done: Bool @objc requis init? (codeur aDecoder: NSCoder) self.done = aDecoder.decodeBool (forKey: "done") super.init (codeur: aDecoder) @objc override func encode (avec aCoder: NSCoder) aCoder.encode (done, forKey: "done") super.encode (avec: aCoder) init (nom: String, done: Bool) self.done = done super.init (nom : prénom)  

le Faire la classe hérite de la Tâche class et déclare une propriété de variable terminé de type Bool. En plus des deux méthodes requises de la NSCoding protocole qu'il hérite de la Tâche classe, il déclare également un initialiseur désigné, init (nom: fait :).

Comme dans Objective-C, le super mot-clé fait référence à la superclasse, la Tâche classe dans cet exemple. Il y a un détail important qui mérite l'attention. Avant d'invoquer le init (nom :) méthode sur la superclasse, chaque propriété déclarée par le Faire la classe doit être initialisée. En d'autres termes, avant la Faire classe délègue l’initialisation à sa super-classe, chaque propriété définie par le Faire la classe doit avoir une valeur initiale valide. Vous pouvez le vérifier en inversant l'ordre des instructions et en inspectant l'erreur qui apparaît..

La même chose s'applique à la init? (codeur :) méthode. Nous initialisons d'abord le terminé propriété avant d'appeler init? (codeur :) sur la superclasse.

Initialisateurs et héritage

Lors du traitement de l'héritage et de l'initialisation, il convient de garder quelques règles à l'esprit. La règle pour les initialiseurs désignés est simple.

  • Un initialiseur désigné doit appeler un initialiseur désigné à partir de sa super-classe. dans le Faire classe, par exemple, le init? (codeur :) méthode invoque le init? (codeur :) méthode de sa super-classe. Ceci est également appelé déléguer.

Les règles relatives aux initialiseurs de commodité sont un peu plus complexes. Il y a deux règles à garder à l'esprit.

  • Un initialiseur de commodité doit toujours appeler un autre initialiseur de la classe dans laquelle il est défini. Tâche classe, par exemple, le init () la méthode est un initialiseur de commodité et délègue l'initialisation à un autre initialiseur, init (nom :) dans l'exemple. Ceci est connu comme déléguer à travers.
  • Même si un initialiseur de confort n'a pas à déléguer l'initialisation à un initialiseur désigné, un initialiseur de confort doit appeler un initialiseur désigné à un moment donné. Cela est nécessaire pour initialiser complètement l'instance en cours d'initialisation.

Avec les deux classes de modèle implémentées, il est temps de refactoriser la ViewController et AddItemViewController Des classes. Commençons par ce dernier.

2. Refactoring AddItemViewController

Étape 1: Mettez à jour le AddItemViewControllerDelegate Protocole

Les seuls changements que nous devons faire dans le AddItemViewController classe sont liés à la AddItemViewControllerDelegate protocole. Dans la déclaration de protocole, changez le type de didAddItem de Chaîne à Faire, la classe de modèle que nous avons implémentée plus tôt.

protocol AddItemViewControllerDelegate contrôleur func (contrôleur _: AddItemViewController, didAddItem: À faire)

Étape 2: Mettez à jour le créer(_:) action

Cela signifie que nous devons également mettre à jour le créer(_:) action dans laquelle nous appelons la méthode déléguée. Dans la mise à jour, nous créons un Faire par exemple, en le passant à la méthode déléguée.

@IBAction func create (_ expéditeur: Any) if let nom = textField.text // Créer un élément let item = ToDo (nom: nom, done: false) // Notifier le délégué délégué? .Controller (self, didAddItem: item )

3. Refactoring ViewController

Étape 1: Mettez à jour le articles Propriété

le ViewController la classe nécessite un peu plus de travail. Nous devons d’abord changer le type de articles propriété à [Faire], un étalage de Faire les instances.

Articles var: [ToDo] = [] didSet (oldValue) let hasItems = items.count> 0 tableView.isHidden =! hasItems messageLabel.isHidden = hasItems

Étape 2: Méthodes de source de données d'affichage de table

Cela signifie également que nous devons repenser quelques autres méthodes, telles que la tableView (_: cellForRowAt :) méthode indiquée ci-dessous. Parce que le articles tableau contient maintenant Faire Il est beaucoup plus simple de vérifier si un élément est marqué comme terminé. Nous utilisons l'opérateur conditionnel ternaire de Swift pour mettre à jour le type d'accessoire de la cellule de vue tableau.

func tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell // Extraire l'élément item = éléments [indexPath.row] // Dequeue Cell laisse cell = tableView.dequeueReusableCell (withIdentifier: "TableViewCell", pour: indexPath ) // Configurez la cellule cell.textLabel? .Text = item.name cell.accessoryType = item.done? .checkmark: aucune cellule de retour.

Lorsque l'utilisateur supprime un élément, il suffit de mettre à jour le articles propriété en supprimant le correspondant Faire exemple. Cela se reflète dans la mise en œuvre de la tableView (_: commit: forRowAt :) méthode montrée ci-dessous.

func tableView (_ tableView: UITableView, commettent une édition de style. UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) if éditerStyle == .delete // Mettre à jour les éléments items.remove (à: indexPath.row) // View Table View tableView.deleteRows (at : [indexPath], avec: .right) // Save State saveItems ()

Étape 3: Méthodes de délégué de la vue tableau

Mise à jour de l’état d’un élément lorsque l’utilisateur appuie sur une ligne gérée dans tableView (_: didSelectRowAt :) méthode. La mise en œuvre de cette UITableViewDelegate la méthode est beaucoup plus simple grâce à la Faire classe.

func tableView (_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) tableView.deselectRow (sous: indexPath, animé: true) // Récupération d'un élément, élément = éléments [indexPath.row] // Mise à jour d'un élément, élément.done =! élément. done // Fetch Cell let cell = tableView.cellForRow (à: indexPath) // Mise à jour de la cellule? .accessoryType = item.done? .checkmark: .none // Etat de sauvegarde saveItems ()

Le correspondant Faire instance est mise à jour et cette modification est reflétée par la vue tabulaire. Pour sauver l'état, nous invoquons saveItems () au lieu de saveCheckedItems ().

Étape 4: Ajouter des méthodes de délégation de contrôleur de vue d'élément

Parce que nous avons mis à jour le AddItemViewControllerDelegate protocole, nous devons également mettre à jour le ViewControllerla mise en œuvre de ce protocole. Le changement, cependant, est simple. Il suffit de mettre à jour la signature de la méthode.

contrôleur func (contrôleur _: AddItemViewController, didAddItem: à faire) // Mettre à jour le source items.append (didAddItem) // Save State saveItems () // Recharger la vue de table tableView.reloadData () // Dismiss Ajouter un contrôleur de vue d'élément animé: vrai)

Étape 5: Enregistrer les éléments

le pathForItems () Méthode

Au lieu de stocker les éléments dans la base de données par défaut des utilisateurs, nous allons les stocker dans le répertoire de documents de l'application. Avant de mettre à jour le loadItems () et saveItems () méthodes, nous allons implémenter une méthode d'assistance nommée pathForItems (). La méthode est privée et renvoie un chemin d'accès, l'emplacement des éléments dans le répertoire de documents.

private func pathForItems () -> String guard laissez documentsDirectory = NSSearchPathForDirectoriesInDomains (.documentDirectory, .userDomainMask, true) .first, laissez url = URL (chaîne: documentsDirectory) else fatalError ("Répertoire de documents non trouvé") return URL. appendingPathComponent ("items"). path

Nous allons d’abord chercher le chemin du répertoire de documents dans le sandbox de l’application en appelant NSSearchPathForDirectoriesInDomains (_: _: _ :). Comme cette méthode retourne un tableau de chaînes, nous saisissons le premier élément.

Notez que nous utilisons un garde déclaration pour vous assurer que la valeur retournée par NSSearchPathForDirectoriesInDomains (_: _: _ :) est valable. Nous jetons une erreur fatale si cette opération échoue. Cela met immédiatement fin à l'application. Pourquoi faisons-nous cela? Si le système d'exploitation ne parvient pas à nous transmettre le chemin d'accès au répertoire de documents, nous avons de plus gros problèmes à nous inquiéter..

La valeur de retour pathForItems () est composé du chemin du répertoire des documents avec la chaîne "articles" annexé.

le loadItems () Méthode

La méthode loadItems change un peu. Nous stockons d’abord le résultat de pathForItems () dans une constante, chemin. Nous désarchivons ensuite l’objet archivé sur ce chemin et le descendons en un tableau facultatif de Faire les instances. Nous utilisons une liaison facultative pour déballer cette option et l'assigner à une constante., articles. dans le si clause, nous affectons la valeur stockée dans articles au articles propriété.

func privé loadItems () let path = pathForItems () si let items = NSKeyedUnarchiver.unarchiveObject (withFile: path) as? [ToDo] self.items = items

le saveItems () Méthode

le saveItems () la méthode est courte et simple. Nous stockons le résultat de pathForItems () dans une constante, chemin, et invoquer archiveRootObject (_: toFile :) sur NSKeyedArchiver, en passant dans le articles propriété et chemin. Nous imprimons le résultat de l'opération sur la console.

fonction privée saveItems () let path = pathForItems () si NSKeyedArchiver.archiveRootObject (self.items, toFile: path) print ("Enregistré avec succès") else print ("Enregistrement échoué")

Étape 6: nettoyer

Terminons par la partie amusante, la suppression de code. Commencez par enlever le éléments vérifiés propriété au sommet puisque nous n'en avons plus besoin. En conséquence, nous pouvons également supprimer le loadCheckedItems () et saveCheckedItems () méthodes, et chaque référence à ces méthodes dans le ViewController classe.

Générez et exécutez l'application pour voir si tout fonctionne toujours. Le modèle de données rend le code de l'application beaucoup plus simple et plus fiable. Grace à Faire classe, la gestion des éléments de notre liste est beaucoup plus facile et moins sujette aux erreurs.

Conclusion

Dans cette leçon, nous avons remanié le modèle de données de notre application. Vous en avez appris davantage sur la programmation orientée objet et l'héritage. L’initialisation d’instance est un concept important dans Swift, alors assurez-vous de bien comprendre ce que nous avons décrit dans cette leçon. Vous pouvez en savoir plus sur l’initialisation et la délégation d’initialiseur dans Swift Programming Language.

En attendant, consultez certains de nos autres cours et tutoriels sur le développement iOS en langage Swift!