Una herramienta personalizada que no se interpondrá en su camino en su aplicación

En vísperas del inicio del curso básico "Desarrollador iOS", hemos preparado otra traducción interesante para ti.










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



La mayoría de ustedes probablemente hayan trabajado o estén trabajando actualmente en una aplicación para la cual gran parte de la funcionalidad depende de la comunicación con el servidor a través de HTTP. Cuando las cosas no funcionan como se esperaba, o simplemente desea comprender una región de código con la que aún no está familiarizado, a menudo es útil observar las solicitudes HTTP que van entre la aplicación y el servidor. ¿Qué solicitudes se hicieron? ¿Qué está enviando exactamente el servidor? Para esto, probablemente use herramientas como Charles Proxy o Wireshark .



Sin embargo, estas herramientas suelen ser bastante difíciles de usar y especialmente de configurar. Es posible que le soliciten que configure su propio certificado SSL y realice muchos pasos no triviales para que el dispositivo confíe en él. También muestran mucha información que quizás nunca necesite para comprender su aplicación. Y al mismo tiempo, es difícil asignarlos a lo que está sucediendo en su aplicación. ¿Qué pasaría si le dijera que existen herramientas que pueden hacer la mayor parte de este trabajo, que requieren menos molestias de su parte para configurarlas y que muestran la información de una manera mucho más comparable a lo que realmente está sucediendo en su aplicación? ?



En preparación para la WWDC 1 de la próxima semana , (re) vi un par de charlas de WWDC anteriores. De todos modos, me perdí por completo que las herramientas principales se reescribieron para unificarlas y facilitar mucho la creación de herramientas personalizadas para Xcode 10. Además, WWDC 2019 demostró ser una gran introducción a las herramientas que me he perdido todos estos años.



Genial, ahora puedes escribir tus propias herramientas para medir cosas que las herramientas normalmente no miden. Pero, ¿qué se puede medir y qué tan fácil es? Yo diría: "casi todo" y "no tan difícil, lo suficientemente rápido". Por lo general, todo lo que necesita hacer es escribir un archivo XML que le diga cómo convertir punteros de señales de su código a datos para mostrar en herramientas, y el XML requerido para hacer esto no es particularmente sofisticado. El principal obstáculo es que el "código" que escribe probablemente sea muy diferente de lo que está acostumbrado, hay muy pocos ejemplos, la documentación solo brinda una descripción general de cómo hacer esto, y aunque Xcode es bastante comprueba estrictamente los archivos XML, no hay autocompletado y hay pocas cosas que puedan facilitar la búsqueda de errores. Pero después de pasar un poco de tiempopuede encontrar los elementos que necesita, y si tiene un ejemplo para adaptar el código, puede hacer las cosas con bastante rapidez. Aquí solo voy a dar un ejemplo e intentar enumerar todos los enlaces útiles.



Pero comencemos por el principio: quiero que cualquiera de ustedes que haya usado Charles o Wireshark antes para depurar su aplicación, o que haya desarrollado una aplicación que hace muchas solicitudes HTTP, pueda crear una herramienta de rastreo HTTP personalizada para su aplicación, o al menos un marco. Se verá así:







me tomó aproximadamente un día construir y depurar este prototipo (después de ver los videos relevantes de la WWDC). Si no está interesado en otra cosa que no sea el código, puede verlo aquí .



Visión de conjunto



La forma más sencilla de crear una herramienta personalizada es usar os_signpost , que es exactamente lo que vamos a hacer aquí. Se usa para registrar los punteros de señalización .event o .begin y .end . Luego, configura una herramienta personalizada para analizar estos intervalos os_signpost y extraer valores adicionales en los que inició sesión, configurar cómo mostrarlos en el gráfico, cómo agruparlos, cuáles filtrar y cómo representar las estructuras de listas o árboles / diagramas de flujo en el panel de detalles de la herramienta. ...



Queremos crear una herramienta que muestre todas las solicitudes HTTP que pasan por nuestra biblioteca de red como intervalos (inicio + fin) para que podamos ver cuánto tardan y correlacionarlas con otros eventos que suceden en nuestra aplicación. Para este artículo, utilizo Alamofire como la biblioteca de redes de la herramienta y Wordpress como mi aplicación de creación de perfiles, simplemente porque son de código abierto. Pero puede adaptar fácilmente todo el código a su biblioteca de red.



Paso 0: echa un vistazo a la aplicación de instrumentos



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




1: signpost-



Queremos construir nuestra herramienta sobre poste indicador, es decir, registraremos nuestros datos a través del poste indicador. Alamofire envía una notificación cada vez que una solicitud comienza o termina, así que todo lo que necesitamos es algo como esto 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)
}




Cuando comienza la solicitud, registramos el poste indicador .begin, cuando se completa, agregamos el poste indicador .end. Para hacer coincidir el final de la llamada con el inicio correspondiente de la llamada, se usa signpostIdpara asegurarnos de cerrar el intervalo correcto si ocurren múltiples solicitudes en paralelo. Idealmente, deberíamos almacenar signpostIden el objeto de solicitud para asegurarnos de que estamos usando el mismo para .beginy .end. Sin embargo, no quería editar el tipo Requesten Alamofire, así que decidí usarlo OSSignpostID(log:, object:)y pasarle el objeto ID. Usamos el objeto base URLSessionTaskporque en ambos casos será el mismo, lo que OSSignpostID(log:, object:)nos permite devolver el mismo identificador cuando es llamado varias veces.



Estamos registrando datos usando una cadena de formato. Probablemente siempre debería separar los dos argumentos con una cadena bien definida para facilitar el análisis en el lado de la herramienta y también para facilitar el análisis. Tenga en cuenta que no es necesario que registre datos en la .endllamada si ya la ha registrado .begin. Se combinarán en un intervalo y tendrá acceso a ellos.



Paso 2: cree un nuevo proyecto de herramienta personalizada en Xcode.



Siga los pasos de Crear instrumentos personalizados (Sesión 410 de WWDC 2018) o Ayuda de la aplicación de instrumentos - Crear un proyecto de caja de herramientas para crear un nuevo proyecto de caja de herramientas en Xcode. Esto le dará un proyecto básico de Xcode con extensión .instrpkg. Allí indicaremos todos los detalles.



Paso 3: haz el resto



Básicamente, seguirá los pasos descritos en la ayuda de la aplicación Instrumentos: creación de una herramienta a partir de datos de señales . Si bien las descripciones de todos los pasos aquí son correctas, aún carecen de muchos detalles, por lo que es mejor tener un ejemplo de una herramienta personalizada real frente a usted. Puedes echarle un vistazo al mío aquí . Básicamente, necesitará las siguientes partes:



Esquema



Esto le dice a las herramientas cómo analizar los datos de sus punteros de señalización en variables que puede usar. Define una plantilla que extrae variables de sus mensajes de registro y las distribuye entre columnas.



<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>




mnemonices el identificador al que se referirá esta columna más adelante. Por alguna razón, me pareció un poco extraño nombrar las columnas de la misma forma que las variables, así que puse un prefijo delante de ellas column. Pero, hasta donde yo sé, no hay necesidad de hacer esto.



Herramienta La



herramienta consta de una definición básica:



<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>




Es bastante simple. La mayoría de estos campos son texto de formato libre o están relacionados con materiales que definió anteriormente ( schema-ref). Pero categorytambién iconsolo puede tener un pequeño conjunto de valores definidos aquí y aquí .



Gráfico dentro de una herramienta



Un gráfico define la parte gráfica de la interfaz de usuario de la herramienta, la representación visual que ve en el área de seguimiento. Se parece a esto:



<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>




Puede tener diferentes carriles y puede utilizar la plantilla de trazado para implementar un número dinámico de parcelas por carril. Mi ejemplo contiene un ejemplo de un gráfico simple . No estoy seguro de por qué graphy lanetengo títulos. Además de esto, cada gráfico plot-templatetambién recibe una etiqueta de label-format.



Lista, agregación o lo que sea para una vista detallada



Con solo un gráfico, las herramientas se verían algo incompletas. También le gustaría mostrar algo en la Vista de detalles. Puede hacer esto con list, aggregationo narrative. Puede que haya incluso más opciones que aún no he cumplido. La agregación se parece a esto:



<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 lista se ve así:



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




Material extra



Esto, de hecho, es todo. Sin embargo, no ha hecho mucho más de lo que describe el video de la WWDC, y prometí llenar algunos vacíos.



Mi herramienta de ejemplo contiene un par de cosas más interesantes.



Una pequeña expresión CLIPS para colorear el intervalo dependiendo de si la solicitud fue exitosa o no . Puede encontrar valores de color en la Referencia de tipos de ingeniería de instrumentos .

Con una plantilla de gráfico, puede mostrar varios gráficos en una franja o, por ejemplo, tener un gráfico por host, como en mi ejemplo. Sin embargo, puede tener más de un nivel de jerarquía y permitir al usuario expandir o contraer partes. Para ello, necesitará utilizar un elemento <engineering-type-track>,para definir su jerarquía y luego agregar (aumento) para diferentes niveles de la jerarquía para agregar gráficos y vistas detalladas . Además, no olvide activar los complementos dentro de la herramienta correspondiente.



Otras acciones



Si aún no lo ha encontrado en los enlaces anteriores, en realidad hay una ayuda completa para todo lo que puede poner en el .instrpkgarchivo. Por ejemplo, le dirá qué elementos <instrument>o qué iconos puede elegir para su herramienta. Un punto importante: el orden importa. Entonces, por ejemplo, en <instrument>, <title>debe aparecer antes que <category>, de lo contrario, la descripción no será válida.



Revise la creación de herramientas personalizadas (Sesión 410 de WWDC 2018) nuevamente para anotar los detalles que pueda necesitar. También hay un código de muestra de la sesión de WWDC 2019 donde encontré un ejemplo de uso <engineering-type-track>.



CLIPS es el lenguaje que se usa para escribir modeladores personalizados (modeladores; no cubrimos eso aquí), pero también se puede usar para expresiones cortas durante las declaraciones de columna. La documentación del idioma es mucho más extensa de lo que necesita. Lo principal que probablemente necesite saber para escribir una expresión simple es que CLIPS usa la notación de prefijo, por ?a + ?blo que debe escribir en su lugar (+ ?a ?b).



Más artículos sobre herramientas personalizadas



Igor sobre la creación de cajas de herramientas personalizadas en Xcode



Depuración



Siempre es una buena idea agregar la herramienta XCodeos_signposta su documento de seguimiento. De esta manera, si algo no funciona como se esperaba, puede verificar si sus datos están registrados correctamente y si su herramienta los interpretó correctamente.



Lo que no he descubierto todavía



  • Cómo utilizar los valores que Instruments le proporciona de forma predeterminada y que muestra en la interfaz de usuario (por ejemplo, duración) en expresiones para definiciones de columna (por ejemplo, para crear una columna de velocidad en baudios dividiendo los bytes recibidos por la duración).
  • Cómo mostrar cualquier cosa en el área de detalles adicionales. Parece que es solo para la pila de llamadas. Me gustaría mostrar, por ejemplo, el cuerpo JSON de la solicitud seleccionada, pero no he encontrado ningún ejemplo que aclare esto.




De que es capaz esta herramienta



Trabajo aún en progreso



Descárguelo y compruébelo usted mismo.



Notas al pie



  1. De acuerdo, en realidad hubo otras razones.
  2. El código completo para iniciar sesión en mi ejemplo está en el archivo Logger.swift . Se asume para Alamofire 4.8 porque eso es lo que usa la versión actual de la aplicación Wordpress para iOS, aunque Alamofire 5 ya está disponible al momento de escribir este artículo. Debido a las notificaciones, este código de registro es fácil de agregar sin cambiar el propio Alamofire, sin embargo, si tiene una biblioteca de red personalizada, puede ser más fácil agregar una entrada a la biblioteca para acceder a más detalles.





Un comienzo rápido para el desarrollo de iOS (seminario web gratuito)






Lee mas






All Articles