
Il existe de nombreux conseils et astuces qui permettent aux développeurs iOS de savoir comment optimiser les performances pour que les animations dans les applications s'exécutent correctement. Après avoir lu l'article, vous comprendrez ce que signifie 16,67 millisecondes pour le développeur iOS et quels outils sont les meilleurs à utiliser pour retrouver le code.
L'article est basé sur le discours-programme prononcé par
Luke Parham , actuellement ingénieur iOS chez Apple et auteur de tutoriels pour le développement iOS sur RayWenderlich.com, à l'International Mobile Developers Conference
MBLT DEV 2017 .
«Hé les gars. Si vous le pouvez, disons, vous pouvez réduire de 10 secondes le temps de démarrage, multiplier cela par 5 millions d'utilisateurs et c'est 50 millions de secondes chaque jour. Sur un an, cela représente probablement des dizaines de vies. Donc, si vous le faites démarrer dix secondes plus vite, vous avez sauvé une dizaine de vies. Ça en vaut vraiment la peine, tu ne crois pas?
Steve Jobs sur les performances (temps de démarrage d'Apple II).Les performances dans iOS ou comment sortir du principal
Le thread principal est chargé d'accepter les entrées des utilisateurs et d'afficher les résultats à l'écran. Accepter les tapotements, les panoramiques, tous les gestes, puis le rendu. La plupart des téléphones mobiles modernes affichent 60 images par seconde. Cela signifie que tout le monde veut faire tout le travail en 16,67 millisecondes. Donc, sortir du fil principal est une chose vraiment importante.
Si quelque chose prend plus de 16,67 millisecondes, vous perdrez automatiquement des images et vos utilisateurs le verront lorsqu'il y aura des animations. Certains appareils ont encore moins de temps à rendre, par exemple, le nouvel iPad a 120 Hertz, il n'y a donc que 8 millisecondes par image pour faire le travail.
Cadres supprimés
Règle n ° 1: utilisez CADisplayLink pour suivre les images perdues
CADisplayLink est une minuterie spéciale qui se déclenche sur le Vsync. Le Vsync est lorsque l'application est rendue à l'écran, et cela se produit toutes les 16 millisecondes. À des fins de test, dans votre AppDelegate, vous pouvez configurer CADisplayLink ajouté à la boucle d'exécution principale, puis avoir simplement une autre fonction où vous faites un peu de calcul. Ensuite, vous suivez depuis combien de temps l'application fonctionne et depuis combien de temps cette dernière fonction a été lancée. Et voyez si cela a pris plus de 16 millisecondes.

Cela ne se déclenche que lorsqu'il est réellement rendu. Si vous faisiez beaucoup de travail et que vous ralentissiez le thread principal, cela s'exécuterait 100 millisecondes plus tard, ce qui signifie que vous avez fait trop de travail et que vous avez perdu des images pendant cette période.
Par exemple, il s'agit de l'application Catstagram. Il a des bégaiements lors du chargement de l'image. Et puis vous pouvez voir que l'image a été supprimée à un certain moment et qu'elle a eu un temps écoulé d'environ 200 millisecondes. Cela signifie que cette application fait quelque chose qui prend trop de temps.

Les utilisateurs n'aiment pas une telle expérience, surtout si l'application prend en charge les appareils plus anciens comme l'iPhone 5, les anciens iPod, etc.
Profileur de temps
Time Profiler est probablement l'outil le plus utile pour retrouver les informations. Les autres outils sont utiles mais, au final, dans Fyusion, nous utilisons Time Profiler comme 90% du temps. Les suspects habituels de l'application sont la vue défilante, le texte et les images.
Les images sont vraiment grandes. Nous avons le décodage JPEG - "UIImageView" est égal à certains UIImage. UIimages décode tous les JPEG de l'application. Ils le font lentement, vous ne pouvez donc pas vraiment suivre directement les performances. Cela ne se produit pas correctement lorsque vous définissez l'image, mais vous pouvez la voir dans les traces du profileur temporel.
La mesure du texte est une autre grande chose. Cela apparaît, par exemple si vous en avez beaucoup de très complexes comme le japonais ou le chinois. Cela peut prendre beaucoup de temps pour effectuer la mesure des lignes.
La disposition de la hiérarchie ralentit également le rendu de l'application. Cela est particulièrement vrai avec la disposition automatique. C'est pratique, mais c'est aussi très lent par rapport à la mise en page manuelle. C'est donc l'un de ces compromis. Si cela ralentit l'application, il est peut-être temps de s'en éloigner et d'essayer une autre technique de mise en page.
Exemple de trace

Dans l'exemple d'arborescence d'appels, vous pouvez voir combien de travail vos CPU font. Vous pouvez changer les vues, les regarder par threads, les regarder par CPU. Habituellement, la chose la plus intéressante est de séparer par threads et de regarder ensuite ce qui se trouve sur main.
Souvent, lorsque vous commencez à regarder cela, cela semble super écrasant. Vous avez parfois le sentiment: «Qu'est-ce que toutes ces ordures? Je ne sais pas ce que cela signifie "FRunLoopDoSource0".
Mais c'est l'une des choses où vous pouvez creuser et comprendre comment les choses fonctionnent et cela commence à avoir un sens. Vous pouvez donc suivre la trace de la pile et regarder toutes les choses du système que vous n'avez pas écrites. Mais en bas, vous pouvez voir votre code réel.
L'arbre d'appel
Par exemple, nous avons une application très simple qui a la fonction principale, puis elle appelle quelques méthodes à l'intérieur de la principale. Le profileur temporel fait qu'il prend un instantané de la trace de votre pile actuellement par défaut toutes les millisecondes. Ensuite, il attend une milliseconde et prend un instantané, où vous avez appelé «principal» qui a appelé «foo» qui a appelé «bar». Il y a la première trace de pile sur la capture d'écran. Donc, cela est collecté. Nous avons ces chiffres: 1, 1, 1.

Chacune de ces fonctions a été appelée une fois. Puis, une milliseconde plus tard, nous capturons une autre pile. Et cette fois, c'est exactement la même chose, nous augmentons tous les décomptes de 2.

Ensuite, à la troisième milliseconde, nous avons une pile d'appels légèrement différente. Main appelle directement «bar». Le bar et le bar sont en hausse de un. Mais alors nous avons une scission. Parfois, les appels principaux «foo», parfois les appels principaux «bar» directement. Cela arrive une fois. Une méthode a été appelée à l'intérieur d'une autre.
Plus loin, une méthode a été appelée à l'intérieur d'une autre qui appelle la troisième méthode. Nous voyons que «buz» a été appelé deux fois. Mais c'est une méthode si petite qu'elle se produit entre une milliseconde.
En utilisant Time Profileer, il est important de se rappeler qu'il ne donne pas les heures exactes. Il ne dit pas exactement combien de temps prend une méthode. Il indique la fréquence à laquelle il apparaît dans les instantanés, ce qui ne peut qu'approcher le temps d'exécution de chaque méthode. Parce que si quelque chose est assez court, il n'apparaîtra jamais.

Si vous passez en mode console dans l'arborescence des appels, vous pouvez voir tous les événements de chute de trame et vous pouvez les faire correspondre. Nous avons un tas d'images en cours de suppression et nous avons un tas de travail en cours. Vous pouvez zoomer dans le profileur temporel et voir ce qui était exécuté uniquement dans cette section.

En fait, dans Mac, en général, vous pouvez cliquer sur les triangles de divulgation et cela s'ouvrira comme par magie et vous montrera ce qui est le plus important là-dedans. Il descendra à ce qui fait le plus de travail. Et 90% du temps, ce sera CFRunLoopRun, puis les rappels.

L'application entière est basée sur une boucle d'exécution. Vous avez cette boucle qui dure indéfiniment, puis à chaque itération de la boucle, les rappels sont appelés. Lorsque vous arrivez à ce point, vous pouvez approfondir chacun de ces éléments et regarder essentiellement quels sont vos trois ou quatre principaux goulots d'étranglement.
Si nous explorons l'un de ces éléments, nous pouvons voir de telles choses où il est vraiment facile de les regarder, et dire: "Wow, je ne sais pas ce que cela fait." Comme les rendus, le fournisseur d'images, IO.

Il existe une option où vous pouvez masquer les bibliothèques système. Il est vraiment tentant de se cacher, mais en réalité, c'est en fait le plus gros goulot d'étranglement de l'application.
Il y a les pondérations qui indiquent le pourcentage du travail effectué par cette fonction ou méthode particulière. Et si nous explorons l'exemple, nous avons 34% et cela se produit à cause d'Apple jpeg_decode_image_all. Après un peu de recherche, il devient clair que cela signifie que le décodage JPEG se produit sur le thread principal et provoque la majorité des pertes d'images.

Règle n ° 2
En règle générale, il est préférable de décoder les JPEG en arrière-plan. La plupart des bibliothèques tierces (AsyncDisplayKit, SDWebImage, ...) le font immédiatement. Si vous ne souhaitez pas utiliser de frameworks, vous pouvez le faire vous-même. Ce que vous faites, c'est que vous passez une image, dans ce cas, c'est une extension de UIImage, puis vous configurez un contexte et vous dessinez l'image manuellement dans un contexte dans un CGBitmap.

Lorsque vous faites cela, vous pouvez appeler la méthode Image () décodée à partir d'un thread d'arrière-plan. Cela retournera toujours l'image décodée. Il n'y a aucun moyen de vérifier si en particulier UIImage est déjà décodé, et vous devez toujours les passer ici. Mais si vous mettez les choses en cache correctement, cela ne fait aucun travail supplémentaire.
Cela est techniquement moins efficace. Utiliser UIimageView est super optimisé, super efficace. Il fera le décodage matériel donc c'est un compromis. Vos images seront décodées plus lentement de cette façon. Mais la bonne chose est que vous pouvez envoyer à une file d'attente en arrière-plan, décoder votre image avec cette méthode que nous venons de voir, puis revenir sur le fil principal et définir votre contenu.

Même si ce travail a pris plus de temps, peut-être qu'il ne s'est pas produit sur le thread principal, il ne bloquait donc pas l'interaction avec l'utilisateur car il ne bloquait pas le défilement. C'est donc une victoire.
Avertissements de mémoire
Tout signe que vous obtenez un avertissement de mémoire que vous souhaitez tout supprimer, supprimez toute la mémoire inutilisée que vous pouvez. Mais si vous rencontrez des problèmes sur les threads d'arrière-plan, l'allocation de ces gros fichiers JPEG décodés prend beaucoup de nouvelle mémoire sur les threads d'arrière-plan.
Cela s'est produit dans l'application Fyuse. Si je passais à un fil d'arrière-plan, décodais tous mes JPEG, dans certains cas sur des téléphones plus anciens, le système le tuerait instantanément. Et c'est parce qu'il envoie un avertissement de mémoire disant: «Hé! Débarrassez-vous de votre mémoire »mais les files d'attente en arrière-plan n'écoutent pas. Que se passe-t-il si vous allouez toutes ces images et que cela se bloque à chaque fois. Le contournement consiste à envoyer une requête ping au thread principal à partir du thread d'arrière-plan.

En général, le thread principal est une file d'attente. Les choses sont mises en file d'attente et se produisent sur le thread principal. Lorsque vous passez en arrière-plan dans Objective-C, vous pouvez utiliser performSelectorOnMainThread: withObject: waitUntilDone:. Cela le placera à la fin de la ligne des files d'attente principales, donc si la file d'attente principale est occupée à traiter les avertissements de mémoire, cet appel de fonction ira à la fin de la ligne et attendra que tous les avertissements de mémoire soient traités avant de faire toute cette lourde allocation mémoire
Dans Swift, c'est plus simple. Vous pouvez effectuer une répartition du bloc vide principal de manière synchrone sur le principal.
Voici un exemple où nous avons nettoyé les choses et nous faisons le décodage d'images sur les files d'attente en arrière-plan. Et le défilement visuel est beaucoup plus joli. Nous avons encore des baisses de trame, mais c'est sur un iPod 5g, c'est donc l'une des pires choses que vous pouvez tester sur qui prend toujours en charge comme iOS 10 et 11.

Lorsque vous avez ces gouttes de cadre, vous pouvez continuer à chercher. Il y a encore du travail qui se produit et qui cause ces chutes de trame. Il y a plus de choses que vous pourriez faire pour l'accélérer.
Pour résumer, ce n'est pas toujours aussi simple, mais si vous avez de petites choses qui prennent beaucoup de temps, vous pouvez les faire en arrière-plan.
Assurez-vous qu'il n'est pas lié à UIKit. De nombreuses classes UIKit ne sont pas thread-safe et vous ne pouvez pas allouer cette UIView en arrière-plan.
Utilisez Core Graphics si vous avez besoin de créer des images en arrière-plan. Ne masquez pas les bibliothèques système. Et n'oubliez pas les avertissements de mémoire.
Ceci est la première partie d'un article basé sur la présentation de Luke Parham. Si vous souhaitez en savoir plus sur le fonctionnement de l'interface utilisateur dans iOS, pourquoi utiliser un chemin d'accès plus précis et quand revenir à la gestion manuelle de la mémoire, lisez la deuxième partie d'un article
ici .
Vidéo
Regardez le discours complet ici: