Évolution de l'architecture mobile de Reddit



Ceci est le premier des articles où nous parlons de l'architecture de l'application Reddit pour iOS. Ici, nous parlons de fonctionnalités qui fonctionnent plus près de l'interface utilisateur. En particulier, la transition vers l'architecture Model-View-Presenter (MVP). Les avantages d'un tel refactoring:

  • Amélioration de la flexibilité, de la clarté et de la maintenabilité du code pour prendre en charge la croissance future et accélérer les itérations.
  • Augmentation des performances de défilement de 1,58 fois.
  • Tests unitaires stimulants. Le nombre de tests est passé de quelques-uns à plus de 200.

Voici un diagramme visuel de notre architecture en couches. Le premier article se concentrera sur les niveaux View et Presenter.


La vue ultime de notre architecture en couches

Conditions préalables au changement


Il y a plus d'un an, nous avons publié l'article «Construire un ruban dans une application iOS Reddit» . Il a expliqué comment générer un flux productif et extensible avec un taux de session remarquable de 99,95% sans se bloquer. Nous avons expliqué comment utiliser l'architecture Model-View-Controller (MVC) et créer des abstractions pour les données de pagination.

Maintenant, Reddit a grandi et continue de croître en tant qu'organisation et en tant que service. Par conséquent, les exigences pour l'application iOS Reddit ont augmenté. Il devrait prendre en charge davantage de demandes de fonctionnalités, des boucles itératives plus rapides et des normes de qualité plus élevées. L'équipe de développement est passée de trois à plus de vingt personnes. L'architecture MVC d'origine ne répond guère à ces exigences, des modifications architecturales ont donc dû être apportées.

L'essence du problème


Au fil du temps, le code a perdu en souplesse et en clarté. Dans la communauté des développeurs iOS, l'abréviation MVC est souvent déchiffrée en tant que Massive View Controller car les contrôleurs de vue se gonflent souvent en objets divins avec plus de mille lignes. Malgré tous nos efforts, le problème a vraiment surgi: la hiérarchie d'héritage est devenue inconfortablement profonde, et les contrôleurs ont commencé à se transformer en objets divins obscurs qui résistent au changement.

Nous avons enfoncé le dernier clou dans le cercueil MVC lorsque nous avons décidé de changer le niveau de présentation de la bande. L'audience de l'application Reddit augmente, de sorte que les performances de défilement sont devenues trop souvent dégradées de 60 FPS à 45-55 FPS. Cela signifie que vous devez réécrire la couche de présentation sur bande tout en conservant l'implémentation d'origine. Mais dans l'architecture MVC existante, nous ne pouvions pas réécrire la couche de présentation sur bande sans dupliquer des milliers de lignes de code.

De plus, de nombreuses parties de la base de code sont difficiles à tester. Le code se trouve dans la classe éprouvée de la couche de présentation et les dépendances sont souvent seules (singleton) ou codées en dur dans la classe elle-même. Nous voulions réaliser la possibilité de tests normaux.

Autres tâches définies pour le refactoring: le nombre de pannes doit rester faible, la nouvelle architecture doit jeter les bases d'une croissance future et ne pas interférer avec les fonctions qui dépendent de l'infrastructure existante. Autrement dit, des changements évolutifs et non révolutionnaires sont nécessaires.

Transition vers MVP


Nous avons décidé que pour résoudre les problèmes ci-dessus, une nouvelle version de l'application était nécessaire. Après avoir considéré plusieurs options, nous avons décidé d'utiliser l'architecture Model-View-Presenter (MVP). MVP répond à tous ces critères, et c'est une architecture bien connue et documentée, il est donc plus facile de former des ingénieurs. Il conserve également le concept de «modèles de vue». Si nécessaire, dans Presenter, vous pouvez créer des objets de modèle de vue selon le principe de la responsabilité exclusive - et les utiliser pour développer nos vues.


Graphique du modèle-vue-présentateur

Se débarrasser de Massive View Controller


Pour les applications iOS, il est supposé que les objets de vue sont des sous-classes d'UIView, les objets de contrôleur sont des sous-classes d'UIViewController et les objets de modèle sont de simples objets ol. Comme le nom UIViewController l'indique, ici la vue et le contrôleur sont combinés en un seul objet. Autrement dit, le modèle MVC sur iOS perd souvent ses avantages en raison de la connexion étroite entre le niveau de présentation et le contrôleur. Fait intéressant, Apple reconnaît lui-même cette connexion .


Souvent, l'architecture Model-View-Controller pour iOS se transforme en

Dans l'architecture MVP, nous prenons ce concept en compte et le formalisons, considérant l'UIViewController comme un objet explicite de la couche de présentation. Le concept de traiter un UIViewController comme un objet de présentation avec un mauvais nom est devenu populaire ces dernières années.

Donc, nous supprimons toute logique étrangère dans nos UIViewControllers. Ensuite, nous assignons Presenter à l'intermédiaire entre la vue et le modèle. Dans ce nouveau rôle, il ignore les objets de vue tels que UIViewController. Notez que Presenter interagit avec la vue via une interface. Théoriquement, vous pouvez changer l'implémentation d'une vue en NSViewController (pour MacOS), etc.


Nous facilitons ViewController en introduisant le présentateur et en partageant les responsabilités

Réflexion sur les options MVP


Comme vous pouvez le voir dans le diagramme MVP, l'architecture est très similaire à MVC. En effet, il y a plus de similitudes que de différences. Une telle architecture permet simplement d'établir la séparation correcte du code de présentation et de la logique métier recherchée par MVC. En fait, tous les dérivés de l'architecture de MV (x), tels que MVP, MVVM, MVAdapter et autres, sont simplement des versions différentes du même concept.

On peut se demander pourquoi nous avons complètement abandonné MVC. En fait, Apple décrit différents types de contrôleurs : pour les modèles, les intermédiaires et la coordination. Honnêtement, nous pourrions peut-être remplacer notre présentateur par un autre contrôleur. Mais ils ont décidé de ne pas le faire, car la plupart des développeurs iOS pensaient pour une raison que UIViewController était synonyme de contrôleur. En utilisant le mot Presenter, nous donnons en quelque sorte le signal que cet objet est significativement différent d'un contrôleur conventionnel avec un certain ensemble de fonctions et de propriétés.

Améliorer la flexibilité, la durabilité et la clarté


«Préférez la composition à l'héritage» est un mantra bien connu dans la programmation d'objets. Avec l'héritage, vous devez prédire l'avenir et construire une énorme taxonomie d'objets. Mais si votre hiérarchie d'héritage «idéalement» commence à s'effondrer en raison de changements imprévus, il est difficile de modifier cette structure rigide. Dans la composition, les objets sont créés à partir d'autres objets et leur délèguent le travail. C'est utile car il est facile de changer le comportement d'un objet au moment de l'exécution en changeant simplement les objets qui le composent. Ces objets composites sont encore plus compréhensibles, car le code est forcé hors de la hiérarchie d'héritage dans une abstraction orientée vers une tâche spécifique.

Une telle compositionnalité est l'un des principaux avantages que l'architecture MVP nous a donné. Vous pouvez désormais modifier le comportement du contrôleur en modifiant simplement la composition d'un Presenter particulier. Nous sommes maintenant moins préoccupés par le déchiffrement de la structure d'héritage complexe et rigide. Enfin, les contrôleurs de vue et les objets Presenter sont plus faciles à comprendre car ils ont un ensemble de tâches plus clair.

En y introduisant Presenter et en déplaçant une partie de la logique du contrôleur de vue, nous avons simplifié la hiérarchie d'héritage du contrôleur. La figure ci-dessous montre que nous avons pu supprimer la classe GalleryFeedViewController, car nous avons mis toute cette logique dans le présentateur. Comme déjà discuté, une telle hiérarchie d'héritage est plus facile à comprendre et moins rigide.


Simplifiez la hiérarchie de l'héritage grâce à la composition

Changement gratuit de l'implémentation de la couche de présentation


Comme indiqué précédemment, les performances de défilement des bandes ont commencé à baisser de 60 FPS à 45-55 FPS. Par conséquent, pour la couche de présentation sur bande, nous avons décidé d'utiliser Texture . Il s'agit d'une plate-forme open source basée sur Apple UIKit qui améliore les performances de l'interface grâce au prétraitement dans le thread d'arrière-plan. Dans l'architecture MVC précédente, nous ne pouvions pas changer l'implémentation de la couche de présentation sans beaucoup de duplication de code.


Avant d'implémenter MVP, vous deviez dupliquer du code superflu dans ViewController qui n'était pas lié à View (orange)

La nouvelle architecture MVP a permis l'introduction du support Texture, plutôt que de réécrire les choses à partir de zéro. Nous venons de mettre toute la logique non-View dans la classe générique Presenter. Ils ont ensuite écrit une nouvelle implémentation de la couche de présentation de texture c et réutilisé le code Presenter. Cela a permis de prendre en charge les deux implémentations de View jusqu'à ce qu'il soit temps de déployer confortablement la bande avec Texture pour tous les utilisateurs.


Après la mise en œuvre de MVP: le code sans affichage est déplacé vers le présentateur partagé

Quel est le résultat? Le diagramme ci-dessous montre une augmentation des performances de défilement de la bande. Nous voulions rester autour de 60 FPS afin d'obtenir un défilement absolument fluide.



Tests unitaires


Bien sûr, nous avons mis en œuvre des tests unitaires non seulement à cause de MVP, mais c'était un facteur important. En particulier, l'architecture MVP a étendu la zone de test en déplaçant le code à un niveau où il est plus facile à vérifier. Un effet secondaire est que les niveaux de vue sont devenus plus simples et doivent donc être testés moins souvent.


Augmentation de la zone de test après le déplacement du code non View en dehors de cette couche

Les tests unitaires ont amélioré la prise en charge de la base de code: ils vous permettent d'apporter des modifications en toute confiance et aident à comprendre quel doit être le comportement correct. Ils rendent également le code plus flexible et plus compréhensible car ils encouragent des méthodes telles que l'injection de dépendances, la composition et la programmation d'abstraction. Le nombre de tests unitaires est passé de quelques-uns à plus de 200.

Analyse critique de MVP dans Reddit


Bien que le passage à MVP ait beaucoup aidé, il y a encore quelques éléments à considérer.

La transition de la bande vers Texture a causé de nouveaux problèmes avec les threads. L'application ne prenait pas initialement en charge l'implémentation asynchrone de View. C'est-à-dire que des erreurs apparaissent inévitablement en cas de non-concordance entre l'état View et l'état de l'application. Par exemple, une vue sur bande peut avoir N enregistrements. Et dans le thread d'arrière-plan, l'état de l'application a tranquillement changé - et contient maintenant moins de N messages. Si l'écart n'est pas résolu, l'application se bloquera simplement lorsque View essaiera d'afficher le Nième article dans le flux.

Les erreurs les plus difficiles à corriger avec les threads. Ils sont difficiles à reproduire, donc ils sont difficiles à déboguer. J'ai dû changer la logique de la demande et recevoir des données pour visualiser le flux. En particulier, nous avons mis en œuvre la «protection» avec l'interdiction de toute modification de la source de données lorsque la vue de la bande subit quelques modifications. Cela et d'autres correctifs mineurs ont réduit le nombre d'erreurs associées au traitement en continu. Cependant, le multithreading asynchrone peut encore être amélioré.

Deuxièmement, la couche Presenter représente une «étape» supplémentaire dans le pipeline. Cette étape a un prix en termes d'augmentation de la complexité du code et de réduction des performances. Parfois, vous voulez simplement exécuter cette logique dans l'UIViewController sur un coup de tête ou parce que vous êtes habitué à le faire. Dans le pire des cas, vous constaterez que Presenter est simplement présent en tant qu'entité, sans aucune logique significative. Dans une telle situation, Presenter ne semble pas justifier son existence.


Parfois, vous pouvez passer d'une couche View à une couche RedditCore sans Presenter

En fait, notre application n'est pas entièrement convertie en architecture MVP. Premièrement, la conversion de chaque UIViewController individuel en Presenter prendra trop de temps - et ne sera pas évolutive. Deuxièmement, comme mentionné dans le paragraphe précédent, parfois Presenter n'est tout simplement pas nécessaire. Comme nous l'avons constaté dans notre travail sur l'implémentation de Texture pour le ruban, Presenter est idéal pour faciliter une MVC massive ou pour implémenter View avec un comportement variable, ou si vous avez une logique complexe à vérifier. Mais parfois, UIViewController est si simple que Presenter n'a aucun sens. C'est donc facultatif. Le présentateur ne doit être implémenté que si nécessaire.

Résumé et plans futurs


La refactorisation de l'architecture MVP dans l'application iOS Reddit a aidé à résoudre de nombreuses tâches. En introduisant la couche Presenter, nous avons progressivement développé l'architecture d'application pour prendre en charge la nouvelle implémentation de la couche de présentation, sans perturber les autres fonctions. Le code est devenu plus clair en facilitant le "massif MVC" - en transférant la logique étrangère à la couche Presenter. Nous avons également donné aux développeurs la possibilité d'itérer plus rapidement et de déployer de nouvelles fonctionnalités. Et considérablement amélioré les tests.

Compte tenu de tout cela, il reste encore un long chemin à parcourir. Nous continuons à créer des objets Presenter et à les améliorer. Nous devons continuer à déplacer la logique étrangère des UIViewControllers au niveau Presenter. Il est également nécessaire que tous les présentateurs soient mieux alignés sur le principe de la responsabilité exclusive. Au final, l'application et l'architecture sont en constante évolution.

Source: https://habr.com/ru/post/fr423531/


All Articles