Un outil personnalisé qui ne vous gênera pas dans votre application

A la veille du début du cours de base "développeur iOS", nous avons préparé une autre traduction intéressante pour vous.










: WWDC 2020, . , , — - , . , , - WWDC



La plupart d'entre vous ont probablement travaillé ou travaillent actuellement sur une application pour laquelle une grande partie des fonctionnalités dépend de la communication avec le serveur via HTTP. Lorsque quelque chose ne fonctionne pas comme prévu, ou que vous souhaitez simplement comprendre une région de code que vous ne connaissez pas déjà, il est souvent utile de regarder les requêtes HTTP entre l'application et le serveur. Quelles demandes ont été faites? Qu'est-ce que le serveur envoie exactement? Vous utilisez probablement des outils comme Charles Proxy ou Wireshark pour cela .



Cependant, ces outils sont souvent assez difficiles à utiliser et surtout à mettre en place. Ils peuvent vous demander de configurer votre propre certificat SSL et d'effectuer de nombreuses étapes non triviales pour que l'appareil lui fasse confiance. Ils affichent également de nombreuses informations dont vous n'aurez peut-être jamais besoin pour comprendre votre application. Et en même temps, ils sont difficiles à mapper à ce qui se passe dans votre application. Et si je vous disais qu'il existe des outils qui peuvent faire la plupart de ce travail, qui nécessitent beaucoup moins de tracas de votre part pour les configurer, et qui affichent les informations d'une manière beaucoup plus comparable à ce qui se passe réellement dans votre application? ?



En préparation de la WWDC 1 de la semaine prochaine , j'ai (re) regardé quelques conférences des précédentes WWDC. Quoi qu'il en soit, j'ai complètement manqué que les outils de base aient été réécrits pour les unifier et faciliter la création d'outils personnalisés pour Xcode 10. De plus, la WWDC 2019 s'est avérée être une excellente introduction aux outils qui me manquaient toutes ces années.



Cool, maintenant vous pouvez écrire vos propres outils pour mesurer des choses que les outils ne mesurent pas normalement. Mais que pouvez-vous mesurer et à quel point est-ce facile? Je dirais: «presque tout» et «pas si difficile, assez vite». En règle générale, tout ce que vous avez à faire est d'écrire un fichier XML qui vous indique comment convertir les pointeurs de panneau de signalisation de votre code en données pour les afficher dans les outils, et le XML requis pour ce faire n'est pas particulièrement sophistiqué. Le principal obstacle est que le «code» que vous écrivez est susceptible d'être très différent de ce à quoi vous êtes habitué, il y a très peu d'exemples, la documentation ne donne qu'un aperçu général de la façon de le faire, et bien que Xcode soit en fait assez vérifie strictement les fichiers XML, il n'y a pas d'auto-complétion et peu de choses peuvent vous faciliter la recherche d'erreurs. Mais après avoir passé un peu de tempsvous pouvez trouver les éléments dont vous avez besoin, et si vous avez un exemple pour adapter le code, vous pouvez faire les choses assez rapidement. Ici, je vais juste donner un exemple et essayer de lister tous les liens utiles.



Mais commençons par le début: je souhaite que tous ceux d'entre vous qui ont déjà utilisé Charles ou Wireshark déboguent votre application, ou ont développé une application qui fait beaucoup de requêtes HTTP, pour pouvoir créer un outil de trace HTTP personnalisé pour votre application, ou au moins un cadre. Cela ressemblera à ceci:







Il m'a fallu environ une journée pour construire et déboguer ce prototype (après avoir regardé les vidéos WWDC pertinentes). Si vous n'êtes intéressé par rien d'autre que le code, vous pouvez le voir ici .



Aperçu



Le moyen le plus simple de créer un outil personnalisé est d'utiliser os_signpost , ce que nous allons faire ici. Vous l'utilisez pour enregistrer les pointeurs de panneau de signalisation .event ou .begin et .end . Ensuite, vous configurez un outil personnalisé pour analyser ces intervalles os_signpost et en extraire les valeurs supplémentaires auxquelles vous vous êtes connecté, définir comment les afficher sur le graphique, comment les regrouper, lesquels filtrer et comment représenter les structures des listes ou des arbres / organigrammes dans le volet de détails de l'outil. ...



Nous voulons créer un outil qui affiche toutes les requêtes HTTP qui transitent par notre bibliothèque Web sous forme d'intervalles (début + fin) afin que nous puissions voir combien de temps elles prennent et les corréler avec d'autres événements se produisant dans notre application. Pour cet article, j'utilise Alamofire comme bibliothèque de mise en réseau de l'outil et Wordpress comme application de profilage, simplement parce qu'ils sont open source. Mais vous pouvez facilement adapter tout le code à votre bibliothèque réseau.



Étape 0: Découvrez l'application Instruments



  1. ( 411 WWDC 2019) — . «Orientation», , (instruments), (tracks), (lanes), (traces), (templates), (detail view) . .
  2. ( 410 WWDC 2018), , . , «Architecture» ( , , ) «Intermediate». , , , - . , , . , .




1: signpost-



Nous voulons construire notre outil sur panneau, c'est-à-dire que nous enregistrerons nos données via panneau. Alamofire envoie une notification à chaque fois qu'une demande commence ou se termine, donc tout ce dont nous avons besoin est quelque chose comme ceci 2 :



NotificationCenter.default.addObserver(forName: Notification.Name.Task.DidResume, object: nil, queue: nil) { (notification) in
    guard let task = notification.userInfo?[Notification.Key.Task] as? URLSessionTask,
        let request = task.originalRequest,
        let url = request.url else {
            return
    }
    let signpostId = OSSignpostID(log: networking, object: task)
    os_signpost(.begin, log: SignpostLog.networking, name: "Request", signpostID: signpostId, "Request Method %{public}@ to host: %{public}@, path: %@, parameters: %@", request.httpMethod ?? "", url.host ?? "Unknown", url.path, url.query ?? "")
}
NotificationCenter.default.addObserver(forName: Notification.Name.Task.DidComplete, object: nil, queue: nil) { (notification) in
    guard let task = notification.userInfo?[Notification.Key.Task] as? URLSessionTask else { return }
    let signpostId = OSSignpostID(log: networking, object: task)
    let statusCode = (task.response as? HTTPURLResponse)?.statusCode ?? 0
    os_signpost(.end, log: SignpostLog.networking, name: "Request", signpostID: signpostId, "Status: %@, Bytes Received: %llu, error: %d, statusCode: %d", "Completed", task.countOfBytesReceived, task.error == nil ? 0 : 1, statusCode)
}




Lorsque la demande commence, nous enregistrons le panneau .begin, quand il se termine, nous ajoutons le panneau .end. Faire correspondre la fin de l'appel avec le début correspondant de l'appel est utilisé signpostIdpour s'assurer que nous fermons l'intervalle correct si plusieurs demandes se produisent en parallèle. Idéalement, nous devrions stocker signpostIddans l'objet de requête pour nous assurer que nous utilisons le même pour .beginet .end. Cependant, je ne voulais pas modifier le type Requestdans Alamofire, j'ai donc décidé d'utiliser OSSignpostID(log:, object:)et de lui transmettre l'objet ID. Nous utilisons l'objet de base URLSessionTaskcar dans les deux cas ce sera le même, ce qui OSSignpostID(log:, object:)nous permet de renvoyer le même identifiant lorsqu'il est appelé plusieurs fois.



Nous enregistrons les données en utilisant la chaîne de format. Vous devriez probablement toujours séparer les deux arguments avec une chaîne bien définie pour faciliter l'analyse du côté outil et aussi pour faciliter l'analyse. Veuillez noter que vous n'avez pas besoin d'enregistrer les données de l' .endappel si vous l'avez déjà connecté .begin. Ils seront combinés en un seul intervalle et vous y aurez accès.



Étape 2: Créez un nouveau projet d'outil personnalisé dans Xcode.



Suivez les étapes de Créer des instruments personnalisés (Session 410 de WWDC 2018) ou de l' aide de l'application Instruments - Créer un projet de boîte à outils pour créer un nouveau projet de boîte à outils dans Xcode. Cela vous donnera un projet Xcode de base avec .instrpkgun fichier . Nous y indiquerons tous les détails.



Étape 3: faites le reste



En gros, vous suivrez les étapes décrites dans l' aide de l'application Instruments - Créer un outil à partir des données du panneau . Bien que les descriptions de toutes les étapes ici soient correctes, elles manquent encore de beaucoup de détails, il est donc préférable d'avoir un exemple d'un véritable outil personnalisé devant vous. Vous pouvez consulter le mien ici . Fondamentalement, vous aurez besoin des parties suivantes:



Schéma



Ceci indique aux outils comment analyser les données de vos pointeurs de panneau de signalisation en variables que vous pouvez utiliser. Vous définissez un modèle qui extrait les variables de vos messages de journal et les répartit sur les colonnes.



<os-signpost-interval-schema>
	<id>org-alamofire-networking-schema</id>
	<title>Alamofire Networking Schema</title>

	<subsystem>"org.alamofire"</subsystem>
	<category>"networking"</category>
	<name>"Request"</name>

	<start-pattern>
	    <message>"Request Method " ?http-method " to host: " ?host ", path: " ?url-path ", parameters: " ?query-parameters</message>
	</start-pattern>
	<end-pattern>
	    <message>"Status: " ?completion-status ", Bytes Received: " ?bytes-received ", error: " ?errored ", statusCode: " ?http-status-code</message>
	</end-pattern>

	<column>
	    <mnemonic>column-http-method</mnemonic>
	    <title>HTTP Method</title>
	    <type>string</type>
	    <expression>?http-method</expression>
	</column>
	<!--      -->
</os-signpost-interval-schema>




mnemonicest l'identifiant auquel vous vous réfèrerez ultérieurement. Pour une raison quelconque, j'ai trouvé un peu étrange de nommer les colonnes de la même manière que les variables, alors j'ai mis un préfixe devant elles column. Mais, pour autant que je sache, il n'est pas nécessaire de le faire.



Tool



Tool se compose d'une définition de base:



<instrument>
    <id>org.alamofire.networking.instrument</id>
    <title>Alamofire</title>
    <category>Behavior</category>
    <purpose>Trace HTTP calls made via Alamofire, grouped by method, host, path, etc.</purpose>
    <icon>Network</icon>
    
    <create-table>
        <id>alamofire-requests</id>
        <schema-ref>org-alamofire-networking-schema</schema-ref>
    </create-table>

    <!--     -->
</instrument>




C'est assez simple. La plupart de ces champs sont du texte de forme libre ou sont liés à des matériaux que vous avez définis précédemment ( schema-ref). Mais categoryaussi iconne peut avoir qu'un petit ensemble de valeurs définies ici et ici .



Graphique dans un outil



Un graphique définit la partie graphique de l'interface utilisateur de l'outil, la représentation visuelle que vous voyez dans la zone de piste. Cela ressemble à quelque chose comme ceci:



<instrument>
    <!--    -->
    <graph>
        <title>HTTP Requests</title>
        <lane>
            <title>the Requests</title>
            <table-ref>alamofire-requests</table-ref>
            
            <plot-template>
                <instance-by>column-host</instance-by>
                <label-format>%s</label-format>
                <value-from>column-url-path</value-from>
                <color-from>column-response</color-from>
                <label-from>column-url-path</label-from>
            </plot-template>
        </lane>
    </graph>
    <!--    --> 
</instrument>




Vous pouvez avoir différentes voies et vous pouvez utiliser le gabarit de tracé pour implémenter un nombre dynamique de tracés par voie. Mon exemple contient un exemple de graphique simple . Je ne sais pas pourquoi graphet lanej'ai des titres. En plus de cela, chaque graphique plot-templatereçoit également une étiquette de label-format.



Liste, agrégation ou autre pour une vue détaillée



Avec juste un graphique, les outils sembleraient quelque peu incomplets. Vous souhaitez également afficher quelque chose dans la vue détaillée. Vous pouvez le faire avec list, aggregationou narrative. Il y a peut-être encore plus d'options que je n'ai pas encore rencontrées. L'agrégation ressemble à ceci:



<instrument>
    <!--    -->
    <aggregation>
        <title>Summary: Completed Requests</title>
        <table-ref>alamofire-requests</table-ref>
        <slice>
                <column>column-completion-status</column>
                <equals><string>Completed</string></equals>
        </slice>
        <hierarchy>
            <level>
                <column>column-host</column>
            </level>
            <level>
                <column>column-url-path</column>
            </level>
        </hierarchy>
        
        <column><count/></column>
        <column><average>duration</average></column>
        <column><max>duration</max></column>
        <column><sum>column-size</sum></column>
        <column><average>column-size</average></column>
    </aggregation>
    <!--    --> 
</instrument>




la liste ressemble à ceci:



<instrument>
    <!--    -->
    <list>
        <title>List: Requests</title>
        <table-ref>alamofire-requests</table-ref>
        <column>start</column>
        <column>duration</column>
        <column>column-host</column>
        <!--   ->
    </list>
    <!--    -->
</instrument>




Matériel bonus



Ceci, en fait, est tout. Cependant, vous n'avez pas fait beaucoup plus que ce que décrit la vidéo de la WWDC, et j'ai promis de combler certaines lacunes.



Mon exemple d'outil contient quelques autres choses intéressantes.



Une petite expression CLIPS pour colorer l'intervalle selon que la requête a réussi ou non . Vous pouvez trouver les valeurs de couleur dans la référence des types d'ingénierie des instruments .

Avec un modèle de graphique, vous pouvez afficher plusieurs graphiques sur une bande, ou par exemple avoir un graphique par hôte, comme dans mon exemple. Cependant, vous pouvez avoir plusieurs niveaux de hiérarchie et permettre à l'utilisateur de développer ou de réduire des pièces. Pour cela, vous devrez utiliser un élément <engineering-type-track>,pour définir votre hiérarchie , puis ajoutez (augmentation) pour différents niveaux de la hiérarchie pour ajouter des graphiques et des vues de détail . N'oubliez pas non plus d' activer les modules complémentaires dans l'outil respectif.



Actions supplémentaires



Si vous ne l'avez pas encore rencontré à partir des liens précédents, il existe en fait une aide complète pour tout ce que vous pouvez mettre dans le .instrpkgfichier. Par exemple, il vous indiquera quels éléments <instrument>ou quelles icônes vous pouvez choisir pour votre outil. Un point important: l'ordre compte. Ainsi, par exemple, dans <instrument>, <title>doit apparaître avant <category>, sinon la description sera invalide.



Revoyez à nouveau la création d'outils personnalisés (Session 410 de la WWDC 2018) pour noter les détails dont vous pourriez avoir besoin. Il existe également un exemple de code de la session WWDC 2019 où j'ai trouvé un exemple d'utilisation <engineering-type-track>.



CLIPS est le langage utilisé pour écrire des modélisateurs personnalisés (modélisateurs - nous ne couvrirons pas cela ici), mais il peut également être utilisé pour des expressions courtes lors des déclarations de colonnes. La documentation linguistique est beaucoup plus complète que ce dont vous avez besoin. La principale chose que vous devez probablement savoir pour écrire une expression simple est que CLIPS utilise la notation de préfixe, ?a + ?bvous devez donc écrire à la place (+ ?a ?b).



Plus d'articles sur les outils personnalisés



Igor sur la création de boîtes à outils personnalisées dans Xcode



Débogage



Il est toujours judicieux d'ajouter l'outil XCodeos_signpostà votre document de trace. De cette façon, si quelque chose ne fonctionne pas comme prévu, vous pouvez vérifier si vos données sont correctement enregistrées et si votre outil les a interprétées correctement.



Ce que je n'ai pas encore compris



  • Comment utiliser les valeurs que Instruments vous fournit par défaut et affiche dans l'interface utilisateur (par exemple, durée) dans des expressions pour les définitions de colonne (par exemple, pour créer une colonne de débit en bauds en divisant les octets reçus par la durée).
  • Comment afficher quoi que ce soit dans la zone de détail supplémentaire. On a l'impression que c'est juste pour la pile d'appels. Je voudrais afficher, par exemple, le corps JSON de la requête sélectionnée, mais je n'ai trouvé aucun exemple qui clarifierait cela.




Ce dont cet outil est capable



Travail encore en cours



Téléchargez-le et voyez par vous-même.



Notes de bas de page



  1. D'accord, il y avait en fait d'autres raisons.
  2. Le code complet de journalisation dans mon exemple se trouve dans le fichier Logger.swift . Il est supposé pour Alamofire 4.8 car c'est ce que la version actuelle de l'application Wordpress iOS utilise, bien qu'Alamofire 5 soit déjà publié au moment de la rédaction. En raison des notifications, ce code de journalisation est facile à ajouter sans modifier Alamofire lui-même, mais si vous avez une bibliothèque de réseau personnalisée, il peut être plus facile d'ajouter une entrée à la bibliothèque elle-même pour accéder à plus de détails.





Un démarrage rapide pour le développement iOS (webinaire gratuit)






Lire la suite






All Articles