Uma ferramenta personalizada que não atrapalhará seu aplicativo

Na véspera do início do curso básico "Desenvolvedor iOS", preparamos outra tradução interessante para você.










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



A maioria de vocês provavelmente já trabalhou ou está trabalhando atualmente em um aplicativo para o qual muitas das funcionalidades dependem da comunicação com o servidor via HTTP. Quando as coisas não funcionam conforme o esperado, ou você apenas deseja compreender uma região do código com a qual ainda não está familiarizado, geralmente é útil observar as solicitações HTTP que vão entre o aplicativo e o servidor. Que pedidos foram feitos? O que exatamente o servidor está enviando? Para isso, você provavelmente usa ferramentas como Charles Proxy ou Wireshark .



No entanto, essas ferramentas costumam ser muito difíceis de usar e, principalmente, de configurar. Eles podem exigir que você configure seu próprio certificado SSL e execute muitas etapas não triviais para fazer com que o dispositivo confie nele. Eles também exibem muitas informações que você pode nunca precisar para entender seu aplicativo. E ao mesmo tempo, eles são difíceis de mapear para o que está acontecendo em seu aplicativo. E se eu lhe dissesse que existem ferramentas que podem fazer a maior parte desse trabalho, que exigem muito menos trabalho de sua configuração e que exibem informações de uma maneira muito mais comparável ao que realmente está acontecendo em seu aplicativo? ?



Em preparação para o WWDC 1 da próxima semana , eu (re) assisti a algumas palestras de WWDCs anteriores. De qualquer forma, perdi completamente que as ferramentas principais foram reescritas para unificá-las e tornar muito mais fácil criar ferramentas personalizadas para o Xcode 10. Além disso, o WWDC 2019 provou ser uma ótima introdução às ferramentas que perdi todos esses anos.



Legal, agora você pode escrever suas próprias ferramentas para medir coisas que as ferramentas normalmente não medem. Mas o que você pode medir e quão fácil é? Eu diria: "quase tudo" e "não tão difícil, rápido o suficiente". Normalmente, tudo o que você precisa fazer é escrever um arquivo XML que informa como converter ponteiros de sinalização de seu código em dados para exibição em ferramentas, e o XML necessário para fazer isso não é particularmente sofisticado. O principal obstáculo é que o "código" que você escreve é ​​provavelmente muito diferente do que você está acostumado, há poucos exemplos, a documentação apenas fornece uma visão geral de como fazer isso, e embora o Xcode seja bastante valida estritamente os arquivos XML, não há preenchimento automático e há poucas coisas que podem facilitar a localização de erros. Mas depois de passar um pouco de tempovocê pode encontrar os elementos de que precisa e, se tiver um exemplo para adaptar o código, poderá fazer as coisas rapidamente. Aqui, vou apenas dar um exemplo e tentar listar todos os links úteis.



Mas vamos começar do início: quero que qualquer um de vocês que tenha usado Charles ou Wireshark antes depure seu aplicativo, ou tenha desenvolvido um aplicativo que faz muitas solicitações HTTP, seja capaz de criar uma ferramenta de rastreamento HTTP personalizada para seu aplicativo, ou pelo menos uma estrutura. Será parecido com isto:







Levei aproximadamente um dia para construir e depurar este protótipo (depois de assistir aos vídeos relevantes da WWDC). Se você não estiver interessado em nada além do código, você pode vê-lo aqui .



Visão geral



A maneira mais fácil de criar uma ferramenta personalizada é usar os_signpost , que é exatamente o que faremos aqui. Use-o para registrar os ponteiros de sinalização .event ou .begin e .end . Em seguida, você configura uma ferramenta customizada para analisar esses intervalos os_signpost e extrair valores adicionais que você registrou nela, configurar como exibi-los em um gráfico, como agrupá-los, quais filtrar e como apresentar as estruturas de listas ou árvores / fluxogramas no painel de detalhes da ferramenta. ...



Queremos criar uma ferramenta que exiba todas as solicitações HTTP que passam por nossa biblioteca da web como intervalos (início + fim) para que possamos ver quanto tempo estão demorando e correlacioná-los com outros eventos que acontecem em nossa aplicação. Para este artigo, uso o Alamofire como a biblioteca de rede da ferramenta e o Wordpress como meu aplicativo de criação de perfil, simplesmente porque são de código aberto. Mas você pode adaptar facilmente todo o código à sua biblioteca de rede.



Etapa 0: Confira o aplicativo Instruments



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




1: signpost-



Queremos construir nossa ferramenta em placa de sinalização, ou seja, vamos registrar nossos dados através de placa de sinalização. Alamofire envia uma notificação sempre que uma solicitação começa ou termina, então tudo que precisamos é algo como 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)
}




Quando a solicitação começa, registramos a placa de sinalização .begin; quando ela é concluída, adicionamos a placa de sinalização .end. Para combinar o final da chamada com o início correspondente da chamada, é usado signpostIdpara garantir que fechamos o intervalo correto se várias solicitações estiverem ocorrendo em paralelo. Idealmente, devemos armazenar signpostIdno objeto de solicitação para ter certeza de que estamos usando o mesmo para .begine .end. Porém, eu não queria editar o tipo Requestno Alamofire, então decidi usar OSSignpostID(log:, object:)e passar o objeto ID para ele. Usamos o objeto base URLSessionTaskporque em ambos os casos ele será o mesmo, o que OSSignpostID(log:, object:)nos permite retornar o mesmo identificador quando ele é chamado várias vezes.



Registramos dados usando a string de formato. Você provavelmente deve sempre separar os dois argumentos com alguma string bem definida para facilitar a análise no lado da ferramenta e também para tornar a análise mais fácil. Observe que você não precisa registrar os dados na .endchamada se já tiver feito o login .begin. Eles serão combinados em um intervalo e você terá acesso a eles.



Etapa 2: Crie um novo projeto de ferramenta personalizada no Xcode.



Siga as etapas de Create Custom Instruments (Session 410 from WWDC 2018) ou Instruments App Help - Create a Toolbox Project para criar um novo projeto de caixa de ferramentas no Xcode. Isso lhe dará um projeto básico do Xcode com .instrpkgum. Vamos indicar todos os detalhes lá.



Etapa 3: faça o resto



Basicamente, você seguirá as etapas descritas na ajuda do Instruments App - Criar uma ferramenta a partir de dados de sinalização . Embora as descrições de todas as etapas aqui estejam corretas, ainda faltam muitos detalhes, por isso é melhor ter um exemplo de uma ferramenta personalizada real à sua frente. Você pode dar uma olhada no meu aqui . Basicamente, você precisará das seguintes partes:



Esquema



Isso informa às ferramentas como analisar os dados de seus ponteiros de sinalização em variáveis ​​que você pode usar. Você define um modelo que extrai variáveis ​​de suas mensagens de log e as distribui pelas colunas.



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




mnemonicé o identificador que você vai referir a esta coluna mais tarde. Por algum motivo, achei um pouco estranho nomear as colunas da mesma forma que as variáveis, então coloquei um prefixo na frente delas column. Mas, pelo que eu sei, não há necessidade de fazer isso.



Ferramenta A



ferramenta consiste em uma definição 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>




É muito simples. A maioria desses campos é texto de formato livre ou está relacionada a materiais que você definiu anteriormente ( schema-ref). Mas categorytambém iconsó pode ter um pequeno conjunto de valores definidos aqui e aqui .



Gráfico dentro de uma ferramenta



Um gráfico define a parte gráfica da interface do usuário da ferramenta, a representação visual que você vê na área de rastreamento. É mais ou menos assim:



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




Você pode ter uma pista diferente e pode usar o modelo de plotagem para implementar um número dinâmico de plotagens por pista. Meu exemplo contém um exemplo de gráfico simples . Não tenho certeza do porquê graphe lanetenho títulos. Além disso, cada gráfico em plot-templatetambém recebe um rótulo de label-format.



Lista, agregação ou qualquer outra coisa para uma visão detalhada



Com apenas um gráfico, as ferramentas pareceriam um tanto incompletas. Você também gostaria de exibir algo na Visualização de detalhes. Você pode fazer isso com list, aggregationou narrative. Pode haver ainda mais opções que ainda não conheci. A agregação se parece com isto:



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




a lista se parece com esta:



<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 de bônus



Isso, na verdade, é tudo. No entanto, você não fez muito mais do que o vídeo da WWDC descreve, e prometi preencher algumas lacunas.



Minha ferramenta de exemplo contém mais algumas coisas legais.



Uma pequena expressão CLIPS para colorir o intervalo, dependendo se a solicitação foi bem-sucedida ou não . Você pode encontrar os valores das cores na Referência de tipo de engenharia de instrumentos .

Com um modelo de gráfico, você pode exibir vários gráficos em uma faixa ou, por exemplo, ter um gráfico por host, como no meu exemplo. No entanto, você pode ter mais de um nível de hierarquia e permitir que o usuário expanda ou recolha partes. Para isso, você precisará usar um elemento <engineering-type-track>,para definir sua hierarquia e, em seguida, adicionar (aumento) para diferentes níveis da hierarquia para adicionar gráficos e visualizações de detalhes . Além disso, não se esqueça de ativar os add-ons dentro da respectiva ferramenta.



Ações futuras



Se você ainda não encontrou nos links anteriores, há uma ajuda completa para tudo o que você pode colocar no .instrpkgarquivo. Por exemplo, ele dirá quais elementos <instrument>ou ícones você pode escolher para sua ferramenta. Um ponto importante: a ordem é importante. Portanto, por exemplo, em <instrument>, <title>deve aparecer antes de <category>, caso contrário, a descrição será inválida.



Revise a criação de ferramentas personalizadas (Sessão 410 do WWDC 2018) novamente para observar os detalhes de que pode precisar. Há também um exemplo de código da sessão WWDC 2019, onde encontrei um exemplo de uso <engineering-type-track>.



CLIPS é a linguagem usada para escrever modeladores personalizados (modeladores - não cobriremos isso aqui), mas também pode ser usada para expressões curtas durante declarações de coluna. A documentação do idioma é muito mais extensa do que você precisa. A principal coisa que você provavelmente precisa saber para escrever uma expressão simples é que o CLIPS usa notação de prefixo, então ?a + ?bvocê deve escrever em seu lugar (+ ?a ?b).



Mais artigos sobre ferramentas personalizadas



Igor sobre a criação de caixas de ferramentas personalizadas no Xcode



Depurando



É sempre uma boa ideia adicionar a ferramenta XCodeos_signpostao documento de rastreamento. Desta forma, se algo não funcionar como esperado, você pode verificar se seus dados estão registrados corretamente e se sua ferramenta os interpretou corretamente.



O que eu não descobri ainda



  • Como usar os valores que Instrumentos fornecem por padrão e exibe na interface do usuário (por exemplo, duração) em expressões para definições de coluna (por exemplo, para criar uma coluna de taxa de transmissão dividindo os bytes recebidos pela duração).
  • Como exibir qualquer coisa na área de detalhes extras. Parece que é apenas para a pilha de chamadas. Gostaria de exibir, por exemplo, o corpo JSON da solicitação selecionada, mas não encontrei nenhum exemplo que esclareça isso.




O que esta ferramenta é capaz de fazer



Trabalho ainda em andamento



Baixe e veja por si mesmo.



Notas de rodapé



  1. Ok, na verdade havia outros motivos.
  2. O código completo para fazer login em meu exemplo está no arquivo Logger.swift . É assumido para o Alamofire 4.8, porque é isso que a versão atual do aplicativo Wordpress iOS usa, embora o Alamofire 5 já tenha sido lançado no momento da escrita. Por causa das notificações, esse código de registro é fácil de adicionar sem alterar o próprio Alamofire; no entanto, se você tiver uma biblioteca de rede personalizada, pode ser mais fácil adicionar uma entrada à própria biblioteca para acessar mais detalhes.





Um início rápido para o desenvolvimento iOS (seminário on-line gratuito)






Consulte Mais informação






All Articles