Il y a quelques années, alors que j'étais encore employé dans un cabinet de conseil en téléphonie mobile, j'ai travaillé sur une application pour une grande banque d'investissement. Les grandes entreprises, en particulier les banques, ont généralement mis en place des processus garantissant la sécurité, la robustesse et la maintenance de leurs logiciels..
Une partie de ce processus a consisté à envoyer le code de l'application que j'ai écrite à un tiers pour révision. Cela ne m'a pas dérangé, car je pensais que mon code était impeccable et que la compagnie de révision dirait la même chose..
Quand leur réponse est revenue, le verdict était différent de ce que je pensais. Bien qu'ils aient déclaré que la qualité du code n'était pas mauvaise, ils ont souligné le fait que le code était difficile à maintenir et à tester (les tests unitaires n'étaient pas très populaires à l'époque dans le développement iOS)..
J'ai écarté leur jugement, pensant que mon code était génial et qu'il était impossible de l'améliorer. Ils doivent juste ne pas comprendre!
J'ai eu l'hybris typique du développeur: nous pensons souvent que ce que nous faisons est excellent et que d'autres ne l'obtiennent pas.
Avec le recul, j'avais tort. Peu de temps après, j'ai commencé à lire certaines des meilleures pratiques. À partir de ce moment, les problèmes de mon code ont commencé à se poser comme un pouce endolori. J'ai réalisé que, comme beaucoup de développeurs iOS, j'avais succombé à quelques pièges classiques de mauvaises pratiques de codage..
L'une des mauvaises pratiques de développement iOS les plus courantes survient lors du passage d'état entre les contrôleurs de vue d'une application. Je suis moi-même tombé dans ce piège dans le passé.
La propagation d'états entre les contrôleurs de vue est essentielle dans toute application iOS. Lorsque vos utilisateurs naviguent sur les écrans de votre application et interagissaient avec celle-ci, vous devez conserver un état global qui enregistre toutes les modifications apportées par l'utilisateur aux données..
Et c’est là que la plupart des développeurs iOS recherchent la solution évidente, mais incorrecte: le modèle singleton.
Le modèle singleton est très rapide à mettre en œuvre, en particulier dans Swift, et cela fonctionne bien. Vous devez simplement ajouter une variable statique à une classe pour conserver une instance partagée de la classe elle-même, et vous avez terminé..
classe Singleton static let shared = Singleton ()
Il est alors facile d'accéder à cette instance partagée à partir de n'importe où dans votre code:
laisser singleton = Singleton.shared
Pour cette raison, de nombreux développeurs pensent avoir trouvé la meilleure solution au problème de la propagation d'états. Mais ils ont tort.
Le motif singleton est en fait considéré comme un anti-motif. Il y a eu de nombreuses discussions à ce sujet dans la communauté du développement. Par exemple, voir cette question relative au dépassement de capacité..
En un mot, les singletons créent les problèmes suivants:
À ce stade, certains développeurs pensent: «Ah, j'ai une meilleure solution. Je vais utiliser le AppDéléguer
au lieu".
Le problème est que le AppDéléguer
classe dans les applications iOS est accessible via le UIApplication
instance partagée:
laisser appDelegate = UIApplication.shared.delegate
Mais l'instance partagée de UIApplication
est lui-même un singleton. Donc tu n'as rien résolu!
La solution à ce problème est l'injection de dépendance. L'injection de dépendance signifie qu'une classe ne récupère ni ne crée ses propres dépendances, mais les reçoit de l'extérieur..
Pour voir comment utiliser l'injection de dépendance dans les applications iOS et comment activer le partage d'état, nous devons d'abord revenir sur l'un des modèles d'architecture fondamentaux des applications iOS: le modèle Model-View-Controller..
En résumé, le modèle MVC indique qu'il existe trois couches dans l'architecture d'une application iOS:
La représentation habituelle du modèle MVC ressemble à ceci:
Le problème est que ce diagramme est faux.
Ce «secret» se cache dans quelques lignes de la documentation d'Apple:
«Il est possible de fusionner les rôles MVC joués par un objet. Ainsi, un objet remplit les rôles de contrôleur et de vue. Dans ce cas, il serait appelé contrôleur de vue. De la même manière, vous pouvez également avoir des objets modèle-contrôleur. ”
De nombreux développeurs pensent que les contrôleurs de vue sont les seuls contrôleurs existant dans une application iOS. Pour cette raison, beaucoup de code finit par être écrit à l'intérieur, faute d'un meilleur emplacement. C'est ce qui amène les développeurs à utiliser des singletons lorsqu'ils ont besoin de propager un état: cela semble être la seule solution possible.
D'après les lignes citées ci-dessus, il est clair que nous pouvons ajouter une nouvelle entité à notre compréhension du modèle MVC: le contrôleur de modèle. Les contrôleurs de modèle traitent le modèle de l'application en remplissant les rôles que le modèle lui-même ne devrait pas remplir. Voici à quoi devrait ressembler le schéma ci-dessus:
L'exemple parfait d'un contrôleur de modèle est utile pour conserver l'état de l'application. Le modèle ne doit représenter que les données de votre application. L'état de l'application ne devrait pas être sa préoccupation.
Cette conservation d'état se termine généralement à l'intérieur des contrôleurs de vue, mais nous avons maintenant un nouvel endroit, le meilleur pour le dire: un contrôleur de modèle. Ce modèle de contrôleur peut ensuite être visualisé pour visualiser les contrôleurs au fur et à mesure qu’ils apparaissent à l’écran par injection de dépendance..
Nous avons résolu l'anti-pattern singleton. Voyons notre solution en pratique avec un exemple.
Nous allons écrire une application simple pour voir un exemple concret de la façon dont cela fonctionne. L'application va afficher votre devis préféré sur un écran et vous permettre de l'éditer sur un deuxième écran..
Cela signifie que notre application aura besoin de deux contrôleurs de vue, qui devront partager l'état. Après avoir vu le fonctionnement de cette solution, vous pouvez étendre le concept aux applications de toute taille et de toute complexité..
Pour commencer, nous avons besoin d’un type de modèle pour représenter les données, qui dans notre cas est un devis. Cela peut être fait avec une simple structure:
struct Quote let text: String laissez author: String
Nous devons ensuite créer un contrôleur de modèle contenant l'état de l'application. Ce contrôleur de modèle doit être une classe. En effet, nous aurons besoin d’une seule instance que nous transmettrons à tous nos contrôleurs de vue. Les types de valeur tels que les structures sont copiés lorsque nous les transmettons. Ils ne constituent donc manifestement pas la bonne solution..
Tous les besoins de notre contrôleur de modèle dans notre exemple sont une propriété dans laquelle il peut conserver le devis actuel. Mais, bien sûr, dans les plus grandes applications, les contrôleurs de modèles peuvent être plus complexes que cela:
class ModelController var quote = Citation (texte: "Deux choses sont infinies: l'univers et la stupidité humaine; et je ne suis pas sûr de l'univers.", auteur: "Albert Einstein")
J'ai assigné une valeur par défaut à la citation Nous aurons donc déjà quelque chose à afficher à l'écran lors du lancement de l'application. Cela n’est pas nécessaire et vous pouvez déclarer la propriété comme étant une propriété initialisée facultative. néant, si vous souhaitez que votre application soit lancée avec un état vide.
Nous avons maintenant le contrôleur de modèle, qui contiendra l'état de notre application. Ensuite, nous avons besoin des contrôleurs de vue qui représenteront les écrans de notre application..
Tout d'abord, nous créons leurs interfaces utilisateur. Voici à quoi ressemblent les deux contrôleurs de vue à l'intérieur du storyboard de l'application.
L'interface du premier contrôleur de vue est composée de deux étiquettes et d'un bouton, assemblés avec de simples contraintes de mise en page automatique. (Vous pouvez en savoir plus sur la mise en page automatique ici sur Envato Tuts +.)
L'interface du second contrôleur de vue est la même, mais elle dispose d'une vue texte pour modifier le texte du devis et d'un champ de texte pour modifier l'auteur..
Les deux contrôleurs de vue sont connectés par une seule séquence de présentation modale, qui provient de la Modifier la citation bouton.
Vous pouvez explorer l'interface et les contraintes des contrôleurs de vue dans le référentiel GitHub..
Nous devons maintenant coder nos contrôleurs de vue. Il est important de garder à l’esprit que nous devons recevoir l’instance de contrôleur de modèle de l’extérieur, par injection de dépendance. Ils doivent donc exposer une propriété à cette fin..
var modelController: ModelController!
Nous pouvons appeler notre premier contrôleur de vue QuoteViewController
. Ce contrôleur de vue a besoin de quelques sorties pour les étiquettes du devis et de l'auteur dans son interface..
class QuoteViewController: UIViewController @IBOutlet faible var quoteTextLabel: UILabel! @IBOutlet faible var quoteAuthorLabel: UILabel! var modelController: ModelController!
Lorsque ce contrôleur de vue s’affiche à l’écran, nous remplissons son interface pour afficher le devis actuel. Nous mettons le code pour le faire dans le contrôleur viewWillAppear (_ :)
méthode.
class QuoteViewController: UIViewController @IBOutlet faible var quoteTextLabel: UILabel! @IBOutlet faible var quoteAuthorLabel: UILabel! var modelController: ModelController! remplacer func viewWillAppear (_ animée: Bool) super.viewWillAppear (animée) let quote = modelController.quote quoteTextLabel.text = quote.text quoteAuthorLabel.text = quote.author
Nous aurions pu mettre ce code à l'intérieur du viewDidLoad ()
méthode à la place, ce qui est assez commun. Le problème, cependant, est que viewDidLoad ()
est appelé une seule fois, lorsque le contrôleur de vue est créé. Dans notre application, nous devons mettre à jour l'interface utilisateur de QuoteViewController
chaque fois qu'il apparaît à l'écran. En effet, l'utilisateur peut modifier la citation sur le deuxième écran..
C’est pourquoi nous utilisons le viewWillAppear (_ :)
méthode au lieu de viewDidLoad ()
. De cette manière, nous pouvons mettre à jour l'interface utilisateur du contrôleur de vue à chaque fois qu'elle apparaît à l'écran. Si vous voulez en savoir plus sur le cycle de vie d'un contrôleur de vue et sur toutes les méthodes appelées, j'ai écrit un article détaillant chacune d'entre elles..
Nous devons maintenant coder le deuxième contrôleur de vue. Nous appellerons celui-ci EditViewController
.
Classe EditViewController: UIViewController @IBOutlet Variable faible textView: UITextView! @IBOutlet faible var textField: UITextField! var modelController: ModelController! remplacer func viewDidLoad () super.viewDidLoad () let quote = modelController.quote textView.text = quote.text textField.text = quote.author
Ce contrôleur de vue est comme le précédent:
Dans ce cas, j'ai utilisé le viewDidLoad ()
méthode parce que ce contrôleur de vue ne s'affiche à l'écran qu'une fois.
Nous devons maintenant passer l'état entre les deux contrôleurs de vue et le mettre à jour lorsque l'utilisateur modifie le devis..
Nous passons l'état de l'application dans le préparer (pour: l'expéditeur :)
méthode de QuoteViewController
. Cette méthode est déclenchée par le segment connecté lorsque l'utilisateur appuie sur le bouton Modifier la citation bouton.
class QuoteViewController: UIViewController @IBOutlet faible var quoteTextLabel: UILabel! @IBOutlet faible var quoteAuthorLabel: UILabel! var modelController: ModelController! override func viewWillAppear (_ animated: Bool) super.viewWillAppear (animated) let quote = modelController.quote quoteTextLabel.text = quote.text quoteAutLabel.text = quote.author ignore la fonction (par exemple: UIStoryboardSegue, le même éditeur?): ) si laissez editViewController = segue.destination as? EditViewController editViewController.modelController = modelController
Ici, nous transmettons l'instance de la ModelController
qui maintient l'état de l'application. C’est là que l’injection de dépendance pour le EditViewController
arrive.
dans le EditViewController
, nous devons mettre à jour l'état avec le nouveau devis entré avant de revenir au contrôleur de vue précédent. Nous pouvons le faire dans une action liée à la sauvegarder bouton:
Classe EditViewController: UIViewController @IBOutlet Variable faible textView: UITextView! @IBOutlet faible var textField: UITextField! var modelController: ModelController! override func viewDidLoad () super.viewDidLoad () let quote = modelController.quote textView.text = quote.text textField.text = quote.author @IBAction func save (_ expéditeur: AnyObject) let newQuote = Quote (texte: textView.text, auteur: textField.text!) modelController.quote = newQuote licencier (animation: true, complétion: nil)
Nous avons presque terminé, mais vous avez peut-être remarqué qu'il nous manque encore quelque chose: le QuoteViewController
passe le ModelController
au EditViewController
par injection de dépendance. Mais qui donne cette instance au QuoteViewController
en premier lieu? N'oubliez pas que lors de l'utilisation d'une injection de dépendance, un contrôleur de vue ne doit pas créer ses propres dépendances. Ceux-ci doivent venir de l'extérieur.
Mais il n'y a pas de contrôleur de vue avant la QuoteViewController
, parce que c'est le premier contrôleur de vue de notre application. Nous avons besoin d’un autre objet pour créer le ModelController
par exemple et de le transmettre à la QuoteViewController
.
Cet objet est le AppDéléguer
. Le rôle du délégué de l'application consiste à répondre aux méthodes de cycle de vie de l'application et à configurer l'application en conséquence. Une de ces méthodes est application (_: didFinishLaunchingWithOptions :)
, qui est appelé dès le lancement de l'application. C’est là que nous créons l’instance du ModelController
et le passer à la QuoteViewController
:
class AppDelegate: UIResponder, UIApplicationDelegate var fenêtre: UIWindow? application func (_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool si let quoteViewController = window? .rootViewController as? QuoteViewController quoteViewController.modelController = ModelController () return true
Notre application est maintenant complète. Chaque contrôleur de vue a accès à l'état global de l'application, mais nous n'utilisons aucun singleton dans notre code..
Vous pouvez télécharger le projet Xcode pour cet exemple d'application dans le tutoriel GitHub repo.
Dans cet article, vous avez vu en quoi l'utilisation de singletons pour propager l'état dans une application iOS est une mauvaise pratique. Les singletons créent beaucoup de problèmes, bien qu’ils soient très faciles à créer et à utiliser.
Nous avons résolu le problème en examinant de plus près le modèle MVC et en comprenant les possibilités qui y sont cachées. Grâce à l'utilisation de contrôleurs de modèles et à l'injection de dépendances, nous avons pu propager l'état de l'application sur tous les contrôleurs de vue sans utiliser de singletons..
Ceci est un exemple d'application simple, mais le concept peut être généralisé aux applications de toute complexité. Il s'agit de la meilleure pratique standard pour propager l'état dans les applications iOS. Je l'utilise maintenant dans toutes les applications que j'écris pour mes clients.
Quelques points à garder à l'esprit lorsque vous développez le concept vers de plus grandes applications:
Restez à l'écoute pour plus de conseils sur le développement d'applications iOS et les meilleures pratiques!