C++, frameworks, conception : Facebook revient sur une décennie d’évolutions de son application iOS

C++, frameworks, conception : Facebook revient sur une décennie d’évolutions de son application iOS

Faire et refaire

Avatar de l'auteur
Vincent Hermann

Publié dans

Société numérique

28/02/2023 10 minutes
3

C++, frameworks, conception : Facebook revient sur une décennie d’évolutions de son application iOS

Plus tôt dans le mois, l’ingénieur Dustin Shahidehpour a publié sur l’un des blogs de Facebook un long article technique sur les évolutions de l’application maison pour iOS. On y apprend comment et pourquoi certaines technologies ont été utilisées, les remaniements qui ont eu lieu, ainsi que l’état actuel du développement.

En 2017, nous nous étions penchés sur un problème de taille, au sens littéral du terme : l’embonpoint toujours plus important des applications. Le sujet avait de multiples racines et conséquences, intensifiant l’usage des réseaux et alourdissant la charge pour les processeurs des appareils.

Plusieurs des développeurs que nous avions interrogés nous avaient confirmé le problème, reconnaissant qu’ils étaient devenus « des enfants gâtés du stockage ». La problématique était cependant plus complexe et n’était pas qu’une simple question de « fénéantise » : les contraintes croissantes sur le développement mobile, les délais imposés et l’obligation de sortir les applications sur Android et iOS participaient activement à l’utilisation de cadriciels (frameworks).

La version iOS de Facebook tenait une place à part, elle était même à l’origine de l’article, même si le sujet s’étendait bien au-delà, y compris à la version Android. Plus tôt dans le mois, Facebook a justement publié un gros billet technique retraçant l’histoire de cette version iOS. Intéressant sur le plan technique, ce billet évoque notamment les difficultés rencontrées dans le temps et les solutions adoptées à chaque fois.

Au début était le web

La première version de l’application n’avait aucun rapport à celle d’aujourd’hui. Elle n’était même pas native : il s’agissait d’une version web, mais certains problèmes émergeaient déjà.

À l’époque, et comme ce fut souvent le cas par la suite, l’ingénieur retrace une inadéquation entre les besoins de l’entreprise et les technologies proposées par Apple. Mais il faut se replacer dans le contexte : on était alors en 2008/2009, iOS ne s’appelait pas encore iOS, et les API (interfaces de programmation pour applications) étaient bien moins nombreuses. Surtout, elles ne couvraient pas les cas de figure qui concernaient à ce moment-là Facebook.

Les modèles de données étaient ainsi gérés par Core Data, au sein duquel les objets étaient variables. Surtout, l’API n’était pas adaptée au News Feed de Facebook, dont l’architecture reposait sur plusieurs threads. En outre, le flux de données était rendu bidirectionnel. Cette architecture, selon Dustin Shahidehpour, a rapidement « exacerbé » la création de « code non déterministe », c’est-à-dire produisant des résultats variables, même quand les entrées sont identiques. De plus, le code était complexe à déboguer et les bugs difficiles à reproduire pour analyse.

L’équipe s’est alors penchée sur React, le cadriciel UI (interface utilisateur) open source et créé par Facebook. La technologie permettrait, notamment par son aspect déclaratif, de se débarrasser de nombreux problèmes liés au flux d’actualités, dont la circulation de données ne se ferait alors plus que dans un sens, facilitant la lecture et l’entretien du code.

Cependant, puisqu’il n’existait pas d’UI déclarative dans iOS, l’équipe s’est mise à créer son propre composant dédié. C’est ainsi qu’est né ComponentKit, dont les sources furent ouvertes quelques mois plus tard. Grâce à lui, l’ingénieur indique que les performances du flux d’actualités ont fait un bond de 50 %. Toujours selon lui, ComponentKit a même inspiré SwiftUI d’Apple, sorti des années plus tard. Ce travail fut, dans les grandes lignes, terminé en 2012, avec une réécriture complète de l'application, qui est depuis considérée comme la première vraie version par Facebook.

L’arrivée des fonctions en pagaille

C’est sans doute le sujet le plus récurrent dans le billet : l’arrivée constante de nouvelles fonctions. On y trouvait celles correspondant à la direction que souhaitait donner Facebook à son application, celles réclamées par les utilisateurs, d’autres, par les entreprises, etc. le nombre n’a cessé d’augmenter, causant de multiples difficultés.

C’était particulièrement le cas en 2015. Maintenant que l’application affichait rapidement ses contenus, les fonctions ont pris leur envol, provoquant un embouteillage, résultat de la politique « le mobile d’abord » lancée par Facebook et d’une arrivée massive de nouvelles contributions.

L’application a alors atteint un point critique. L’architecture, à ce moment, était quasi monolithique. Toutes les fonctions étaient ensuite chargées au lancement de l’application, dont le temps de démarrage a gonflé démesurément. Selon Shahidehpour, il atteignait presque 30 secondes, temps laissé par iOS pour considérer que le démarrage est normal et au-delà duquel l’application était fermée de force. Il y avait bien un système de modules, mais il permettait des accès sans restriction aux ressources de l’application.

Décision a alors été prise de déplacer d’importants pans de code vers un conteneur nommé dynamic library, ou dylib. Chaque fonction déplacée ne se chargeait alors plus au démarrage, mais uniquement quand on en avait besoin. La problématique est connue bien sûr, notamment dans les jeux : trouver un juste équilibre entre chargement initial et chargements dans l’application.

Trois dylibs furent ainsi créées, dont une dédiée au code partagé entre les bibliothèques dynamiques et le binaire principal de l’application. Selon Facebook, la solution a fonctionné « magnifiquement », abaissant radicalement le temps de démarrage et permettant d’aborder le futur avec plus de sérénité. Tout n’était pas rose pour autant, car cette manière de faire avait ses propres problèmes, notamment parce que des API de type runtime ne démarraient parfois pas, à cause de code nécessaire situé dans une bibliothèque non chargée.

Les années suivantes furent consacrées à la résolution de ces soucis et à adapter le reste de l’architecture à ce nouveau découpage, nettement plus modulaire.

2017 : on reprend tout depuis le début

En 2017 pourtant, l’équipe était d’accord : il fallait repenser à nouveau l’organisation générale pour trouver une solution plus adaptée. Il s’agissait en particulier de faire face à deux difficultés : le système d’inscription des modules était toujours basé sur un runtime et les ingénieurs avaient besoin de savoir si tout chemin d’exécution au démarrage pouvait déclencher le chargement d’une bibliothèque dynamique.

L’équipe s’est alors tournée vers Buck, un système de compilation open source créé aussi par Meta. Avec Buck, chaque cible (application, dylib, bibliothèque, etc.) est déclarée avec une configuration et la totalité des informations nécessaires à sa compilation, comme les sources, dépendances, flags et autres. L’ensemble permet de produire une vue holistique des classes et fonctions de l’application pendant la compilation.

Ces informations ont été utilisées pour concevoir l’architecture suivante, car l’équipe s’est rendu compte qu’elle pouvait générer à la volée une « carte » précise de toutes les fonctions et classes ayant besoin du moindre élément situé dans une dylib. Les avantages ont été nombreux, dont l’abstraction vis-à-vis des dylib et la génération de code. L’ancienne architecture, basée sur un système de runtime et de modules, a progressivement laissé sa place à un système de plugins.

Un nombre croissant de composants de l’infrastructure ont alors migré vers différents plugins, avec un gros bonus à la clé pour l’équipe de développement : les échecs du runtime étaient majoritairement remplacés par de « simples » avertissements à la compilation, augmentant la traçabilité des actions et simplifiant de beaucoup la maintenance. Autre avantage conséquent de cette approche, la possibilité de partager simplement du code avec d’autres applications, ce qui intéressait bien sûr Meta.

En revanche, et comme on commence à en avoir l’habitude, la solution adoptée n’était pas sans inconvénients. Les erreurs des plugins n’étaient ainsi pas sur Stack Overflow et étaient plus difficiles à déboguer, la stratégie adoptée était très loin du développement traditionnel sur iOS, il n’y avait pas de registre central des fonctions, etc.

Aujourd'hui, le retour vers des technologies natives

À partir de 2020, une autre transition est apparue. Toujours selon l’ingénieur de Facebook, les technologies proposées par Apple avaient largement évolué depuis le temps. D’ailleurs, des modifications dans le SDK (Software development kit) d’iOS avaient forcé l’équipe à revoir certaines approches architecturales.

Un point particulièrement a remis l’équipe au travail : les nouvelles API proposées étaient en grande majorité accessibles via le langage Swift uniquement. Des membres de l’équipe commençaient aussi à réclamer la possibilité de développer en Swift. Décision a donc été prise que plus aucune API tournée vers le produit ne devait contenir de C++, utilisé massivement jusqu’ici (avec Objective-C++), puisque le langage n’a aucune interopérabilité à ce jour avec Swift (ou réciproquement).

Toujours en se servant des mêmes capacités d’abstractions fournies par le système basé sur Buck, les implémentations C++ ont été masquées, tout en restant disponibles pour les fonctions qui les exploitaient. Tous les nouveaux développements sont depuis réalisés en Swift, ou presque.

Actuellement, l’application Facebook fait donc le trajet inverse de ses premiers jours, revenant petit à petit vers les technologies natives d’Apple, maintenant que Facebook dit avoir globalement ce qu’il lui faut. Avec pour conséquence le retrait progressif des cadriciels dédiés et un retour vers ce que l’entreprise nomme « l’orthodoxie de la plateforme ».

Il y a des avantages à la clé bien sûr, notamment en termes d’intégration et, dans certains cas, de performances. Mais c’est un « voyage » dont Facebook estime qu’il n’est « terminé qu’à 1 % ». En outre – et c’est sans doute le point central de ce billet technique – la technologie évolue, de même que les besoins. Aucune solution envisagée ne peut être considérée comme ultime et définitive.

Cette transition est donc en cours, car pertinente actuellement, ce qui ne garantit rien pour la suite. L’ingénieur indique quand même que certains principes apparus avec le temps sont gardés, comme la minimisation du temps de lancement, et la limitation du poids de l’application.

Par exemple, lors de notre article de 2017, nous indiquions que l’application pesait environ 230 Mo sur un iPhone 8. Ce poids a grimpé jusqu’à 330 Mo avec les versions suivantes, avant de redescendre à 300 Mo et se stabiliser.

Dustin Shahidehpour évoque d’ailleurs un point que nous avions mentionné il y a presque six ans : ce que les équipes de développement peuvent se permettre en fonction de la puissance du matériel ciblé. L’ingénieur indique à ce sujet que tous les derniers téléphones d’Apple sont très rapides : « le coût du chargement est bien moindre qu’il l’était ». Ce point avait fait débat dans nos commentaires, car il mettait en lumière la question de l’optimisation du code.

Notez qu’il n’y a pour l’instant pas de billet équivalent pour Android. En revanche, Meta a publié en novembre dernier un article consacré à l’abandon progressif de Java pour Kotlin sur sa base de code Android, pour l’ensemble de ses applications. L’entreprise y explique notamment comment elle est passée de 0 à 10 millions de lignes de code en Kotlin.

Écrit par Vincent Hermann

Tiens, en parlant de ça :

Sommaire de l'article

Introduction

Au début était le web

L’arrivée des fonctions en pagaille

2017 : on reprend tout depuis le début

Aujourd'hui, le retour vers des technologies natives

Commentaires (3)


A-t-on une idée, même grossière, du nombre de personnes qui travaillent sur le développement de l’application Facebook pour iOS ?


Je n’ai pas compris la phrase “Les erreurs des plugins n’étaient ainsi pas sur Stack Overflow”.


Leur système de plugins étant propriétaire et donc non répandu ou utilisé par des équipes externes ou d’autres entreprises, il n’était pas possible pour les développeurs de trouver des réponses sur Stackoverflow lorsqu’ils étaient confrontés à une erreur ou un problème.