
Parfois, vous pouvez vous retrouver dans une situation où votre application ne fonctionne pas bien. Voici donc quelques instruments que vous pouvez utiliser et les meilleures pratiques que vous pouvez mettre en œuvre pour améliorer les choses.
Il s'agit de la deuxième partie de l'article basée sur le discours-programme prononcé par Luke Parham, ingénieur iOS chez Fyusion et auteur de tutoriels pour le développement iOS sur RayWenderlich.com, à l'International Mobile Developers Conference
MBLT DEV en 2017.
Instrument d'animation de base
Si vous avez fait beaucoup de profilage et que vous avez trouvé tous vos goulots d'étranglement, il y a parfois des problèmes de performances. C'est à cause de la façon dont les choses de l'interface utilisateur fonctionnent dans iOS. Chaque fois que vous définissez des cadres ou créez des UIViews, ce qui se passe réellement sous le capot, c'est que vous effectuez une CATransaction ou que le système le fait pour vous. Et ceux-ci sont expédiés vers une chose appelée «le serveur de rendu». Le serveur de rendu est chargé de faire des animations. Si vous faites un UIView animateWith: peu importe, tout cela se produira sur le serveur de rendu qui est un autre thread et il gère toutes les animations de l'application.

Voici un Time Profiler qui a une jauge de fréquence d'images en haut. Et en bas se trouvent les options de débogage les plus importantes. Nous allons parler des deux choses les plus importantes et les plus faciles à corriger.

Le premier est les couches de couleurs mélangées. C'est vraiment très facile à corriger. Et cela nous amène à la première section de la police de performance. Fondamentalement, beaucoup d'applications ont des problèmes: même iMessage, l'application Apple bien-aimée, fait beaucoup de choses pas vraiment géniales. Ici, nous voyons qu'il y a beaucoup de rouge:

Le rouge signifie que vous avez des étiquettes sur fond blanc. Et puis ils sont au-dessus d'un autre fond blanc et pour une raison quelconque, ils ne sont pas définis pour être opaques. Le mélangeur mélange donc ces couleurs, le blanc et le blanc et obtient ainsi une couleur blanche. Pour chaque pixel qui a du rouge, il effectue des calculs supplémentaires sans aucun avantage, vous obtenez toujours du blanc en arrière-plan.
Pour éviter cela, vous pouvez rendre les calques opaques dans la mesure du possible s'ils sont de la même couleur sur la même couleur. Si la sous-vue a la même couleur d'arrière-plan, le mélange n'est pas nécessaire. Tout ce que vous avez à faire est de définir l'opacité de vos calques sur 1, puis de vous assurer que la couleur d'arrière-plan est définie. Si votre couleur d'arrière-plan est claire, elle ne sera pas toujours opaque.

Rendu hors écran
Les éléments rendus hors écran seront affichés en jaune si vous activez cette option. La bonne chose à propos de l'instrument Core Animation est que vous pouvez voir d'autres applications. Vous pouvez activer ces options, puis accéder à n'importe quelle application de votre système et voir ce qu'elles font de mal. Dans ce cas, Instagram a ces petites bulles en haut qui vous montrent les histoires des gens.

Comme vous pouvez le voir, ils sont tous jaunes. Sur iPhone 5, ils sont extrêmement lents. Et c'est parce que le rendu hors écran est bien pire que le mélange alpha. Il bloque le GPU. Il finit par devoir faire des calculs supplémentaires dans les deux sens entre le GPU et le CPU, de sorte que vous obtenez des décrochages supplémentaires qui sont inutiles la plupart du temps.
Chemin de Bézier au lieu de voir les virages
La règle suivante: n'utilisez pas la propriété de rayon de coin. Si vous avez une vue et que vous définissez view.layer.cornerRadius, cela introduit toujours le rendu hors écran. Au lieu de cela, vous pouvez utiliser un chemin bezier et le même type de contenu CGBitmap que précédemment. Dans ce cas, un contexte UIGraphics. Cette fonction fonctionne avec UIImage, elle prend une taille, fait des coins arrondis en fonction de cette taille et utilise un chemin bezier pour couper. Ensuite, nous découpons l'image et la renvoyons du contexte UIImage. Ainsi, cela renverra une image pré-arrondie au lieu d'arrondir la vue dans laquelle l'image se trouve à l'intérieur.

Le dernier exemple. Voici Twitter et voici une vue en temps réel de cette animation en cours d'exécution. Il est censé s'ouvrir et vous montrer les informations, mais tout ce texte et tout cela a été rendu hors écran, ce qui a ralenti l'animation à une exploration. C'est la chose la moins performante que j'ai jamais trouvée dans une application sur l'App Store.

Alors, comment est-ce arrivé? Une chose qui fait que cela se produit est la propriété shouldRasterize d'un CALayer. C'est une option sur un calque qui vous permet de mettre en cache les textures qui ont été rendues. Il y a beaucoup de règles étranges. Comme s'il n'avait pas été utilisé pendant un certain nombre de millisecondes, il quittera le cache. Et puis, s'il quitte le cache, il sera rendu hors écran sur chaque image. Cela ne vaut pas vraiment les avantages possibles. Et il est difficile de vérifier si cela vous profite réellement.
Résumé
Évitez le rendu hors écran et le mélange alpha si vous le pouvez. Le mélange alpha est parfois nécessaire. C'est mieux que le rendu hors écran. Le rendu hors écran se produit pour plusieurs raisons. Cela peut arriver à partir des ombres; cela peut arriver à partir d'un arrondi de coin; cela peut arriver par masquage.
Rendez les vues opaques lorsque cela est possible. N'utilisez pas autant que possible la propriété de rayon d'angle. Utilisez les chemins de Bézier. N'utilisez pas non plus les propriétés d'ombre du calque si vous effectuez des ombres de texte. Vous pouvez utiliser NSShadow à la place.
Trace d'activité
La trace d'activité est une sorte de version de bas niveau de quelque chose que le profileur de temps ferait. Il vous donne une vue de tous vos fils et comment ils interagissent. Et c'est assez compliqué. Mais il a de très belles fonctionnalités que vous pouvez configurer.
Trace système
Utilisez la
trace du système pour suivre les heures d'événements spécifiques. Vous pouvez définir des moyens de suivre des événements et des sections de code spécifiques et voir combien de temps ils prennent dans une application réelle. Il vous permet d'obtenir des informations précises sur ce qui se passe dans votre système.
- Utilisez «Signer les messages» pour signaler quand quelque chose d'important se produit.
- Les points sont des événements uniques lorsque / si vous voulez voir comme une animation s'est produite ou quelque chose comme ça.
- Les régions ont un début et une fin. Pour le décodage d'images, vous pouvez voir quand il commence et quand il se termine afin que vous puissiez estimer combien de temps cela a pris en général.

Voici comment configurer un modèle de trace système. Vous faites cette liste des événements qui peuvent se produire. Donc, le numéro un est un téléchargement d'image. Deux est un décodage d'image, et trois est cette animation d'inclinaison que j'ai ajoutée. Fondamentalement, vous configurez des options supplémentaires pour voir quelles couleurs vont être. Fondamentalement, vous lui envoyez un numéro comme 1 ou 2, il sera rouge ou vert en fonction de ce que vous y envoyez.

Si vous êtes dans Objective-C, vous devez importer cet en-tête kdebug_signpost. Dans Swift, il est juste disponible pour vous.

Et puis vous devez appeler cette fonction, soit kdebug_signpost ou kdebug_signpost_start et kdebug_ signpost_end. Et ils fonctionnent à partir du code que vous avez transmis. Nous avons donc mis en place ces trois événements avec ces chiffres. Ensuite, vous passez ce numéro ici. Vous lui passez un objet qui est fondamentalement la clé de cet événement. Et puis, le dernier nombre est la couleur. Donc 2, c'est comme si vous connaissiez le rouge ou quelque chose.
J'ai un exemple de projet Swift sur
GitHub . J'ai en quelque sorte simplifié les choses. Il y a un début et une fin qui sont un peu plus faciles à gérer.
Voici à quoi cela ressemblera une fois que vous aurez exécuté une trace. Il ne vous montrera rien au début. Ensuite, lorsque vous tuez l'application, elle fera des calculs et vous montrera des choses ici.

Ici, nous pouvons voir nos téléchargements d'images qui ont pris environ 200 millisecondes. Et puis, il y a le décodage d'image qui a pris environ 40 millisecondes. C'est vraiment cool si vous avez un tas de trucs fous dans votre application. Vous pouvez configurer tous ces événements, puis voir simplement la durée de chaque prise et la façon dont ils interagissent les uns avec les autres. C'est tout pour la trace du système.
Bonus
Jetez un œil à l'exemple d'un ralentissement de la caméra où nous pouvons voir ce qui se passe s'il y a des choses AR dans l'application:

Nous avons appliqué un effet et cela prenait 26,4% de tous les calculs sur chaque image juste pour calculer un effet. Et cela ralentissait l'appareil photo à quelque chose de fou comme 10 images par seconde.
Quand j'ai creusé ici et regardé ce gros goulot d'étranglement, j'ai vu que la principale chose qui faisait la plupart du travail était l'utilisation de NSDispatchData intense.

Il s'agit d'une sous-classe de NSData. Et tout cela, c'est obtenir des octets avec la fonction de plage. Et c'est une fonction simple. Tout ce qu'il fait, c'est qu'il prend quelques octets d'une donnée et le place ailleurs. Ce n'est pas trop fou, mais apparemment, tout ce qu'il faisait en interne absorbait 18% de ces 26%.
Règle n ° 1C'est un NSData et il obtient des octets. C'est une chose Objective-C simple, mais si vous rencontrez cela et qu'il s'agit d'un goulot d'étranglement, il est temps de passer à l'utilisation de C à la place. Étant donné que le goulot d'étranglement était autour d'un appel pour récupérer les valeurs flottantes, vous pouvez simplement utiliser memcpy (). Avec memcpy (), vous pouvez déplacer un bloc de données ailleurs. Réduit beaucoup de frais généraux.
Si vous regardez comme NSData, ces classes sont comme des milliers de lignes. Il se passe donc beaucoup de choses là-dedans. Dans ce cas, nous avons l'original en rouge.

Ici, vous obtenez une plage, prenez quelques octets et copiez-les dans le tampon. La version memcpy () est presque exactement la même chose. Ça n'a pas l'air plus compliqué et ça fait agressivement moins de choses.

Lorsque nous changeons cela et que nous l'exécutons à nouveau, les choses sont passées de 26% à 0,6% en changeant cette ligne en memcpy (). Et puis, la fréquence d'images a considérablement augmenté.
Règle n ° 2Évitez de trop tirer si vous faites une sorte d'application de rendu ou même si vous faites quelque chose comme une barre de chargement. Souvent, les événements se produisent plus de 60 images par seconde. Dans ce cas, vous pouvez limiter cette mise à jour de l'interface utilisateur à l'aide d'un CADisplayLink. Il possède une propriété appelée PreferredFramesPerSecond. C'est uniquement pour iOS 10 ou supérieur. Pour les plus anciens, vous devez le faire manuellement mais c'est toujours utile.

Vous pouvez définir la fréquence d'images souhaitée. Souvent, pour des barres de chargement similaires, je le définirai autour de 15 images par seconde, car cela n'a pas vraiment d'importance. Il n'a pas besoin de mettre à jour 60 images par seconde. Cela peut vous faire économiser beaucoup de travail que vous effectuez réellement si les choses se ressemblent de toute façon.
Règle n ° 3Utilisez la mise en cache IMP. Ceci n'est utile que pour Objective-C. InObjective-C, lorsque vous appelez une méthode sous le capot, vous appelez en fait la fonction d'envoi de message Objective-C (objc_msgSend ()). Si vous voyez ces appels dans des traces prendre beaucoup de temps, c'est quelque chose dont vous pouvez vous débarrasser facilement. Il s'agit essentiellement de la table de cache où vous recherchez des pointeurs de fonction en lui donnant le nom d'une méthode. Au lieu de faire cette recherche à chaque fois, vous pouvez mettre en cache le pointeur de fonction et l'appeler directement. Il est généralement au moins deux fois plus rapide.

Si vous n'avez pas de pointeur mis en cache, vous pouvez le récupérer en appelant methodForSelector:. Ensuite, nous appelons simplement cette méthode comme un appel de fonction normal. Vous passez l'objet le sélecteur, puis tous les arguments viennent après.
Règle n ° 4N'utilisez pas ARC. ARC est quelque chose qui ajoute un tas de frais généraux. Dans votre code, vous avez tout ce qui se passe et il arrose tout de retenues et de versions. Il en fait autant qu'il le doit, et il en fait beaucoup plus. Donc, si vous voulez vraiment optimiser, si vous voyez que vous avez un tas d'appels de conservation et de libération dans votre trace et qu'ils prennent beaucoup de temps, vous pouvez simplement passer à ne pas utiliser ARC, ce qui représente beaucoup plus de travail.
Il est également difficile d'amener vos coéquipiers à accepter de le faire et à ne pas en être fou.
N'utilisez pas Swift s'il est particulièrement sensible aux performances. Swift est une belle langue. Il a des fonctionnalités vraiment soignées. Mais il utilise également plus de passe-partout à l'intérieur pour obtenir un haut niveau de fonctionnalité. Si vous voulez être rapide, vous devez vous rapprocher le plus possible de l'assemblage le plus bas possible. Et ce sera plus rapide car c'est moins de code automatiquement.
Si vous regardez dans les trucs si vous pensiez que c'était intéressant, il y a un très bon livre intitulé "iOS et MacOS: Performance Tuning" par Marcel Weiher. Cela va vraiment en profondeur dans beaucoup de ces choses et bien plus encore. J'ai aussi une série vidéo. Je fais des vidéos pour RayWenderlich. Il y a une série d'instruments pratiques que j'ai faite qui va plus en profondeur et qui explique ces choses un peu plus et a quelques exemples. Donc, si vous voulez en savoir plus sur les instruments en particulier, vous pouvez regarder cette série vidéo. Et puis, les vidéos de la WWDC - il y en a des tonnes qui expliquent différentes performances comme celle-ci.
Vidéo
Vous trouverez ici la première partie d'un article basé sur le discours de Luke. Regardez le discours complet ici: