一个自定义工具,不会妨碍您的应用程序

在基础课程“ iOS开发人员”开始前夕,我们为您准备了另一种有趣的翻译。










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



你们中的大多数人可能已经或正在使用某个应用程序,该应用程序的许多功能取决于通过HTTP与服务器进行通信。当某些事情无法按预期工作时,或者您只想了解一个您不熟悉的代码区域时,查看在应用程序和服务器之间的HTTP请求通常会很有帮助。提出了什么要求?服务器到底发送什么?为此,您可能使用诸如Charles ProxyWireshark之类的工具



但是,这些工具通常很难使用,尤其是难以设置。他们可能会要求您设置自己的SSL证书并执行许多重要步骤,以使设备信任它。它们还会显示很多您可能不需要了解您的应用程序的信息。同时,它们很难映射到应用程序中正在发生的事情。如果我告诉您,那里有可以完成大部分工作的工具,这些工具不需要您花费太多的麻烦就可以设置它们,并且以与您的应用程序中实际发生的情况相比更可比的方式显示信息,该怎么办? ?



为了准备下周的WWDC 1,我(重新)观看了先前WWDC的一些演讲。无论如何,我完全错过了重写核心工具以统一它们并使其更容易为Xcode 10创建自定义工具的功能。此外,WWDC 2019成为对我多年来一直缺少的工具很好介绍



很酷,现在您可以编写自己的工具来衡量工具通常无法衡量的事情。但是您能测量什么,它有多容易?我会说:“几乎所有内容”和“不是那么困难,足够快”。通常,您要做的就是编写一个XML文件,该文件告诉您如何将路标指针从代码转换为数据以在工具中显示,而这样做所需的XML并不是特别花哨。主要障碍是您编写的“代码”可能与您习惯使用的代码有很大不同,示例很少,文档仅提供了有关如何执行此操作的概述,尽管Xcode实际上非常有用。严格验证XML文件,没有自动完成功能,几乎没有什么可以使您更容易发现错误。但是花了一点时间您可以找到所需的元素,并且如果您有修改代码的示例,则可以很快完成工作。在这里,我将举一个例子,并尝试列出所有有用的链接。



但是,让我们从头开始:我希望你们中的任何人以前使用Charles或Wireshark调试应用程序,或者已经开发了发出大量HTTP请求的应用程序,以便能够为您的应用程序定制HTTP跟踪工具,或者至少是一个框架。它看起来像这样:







我花了大约一天的时间来构建和调试该原型(在观看了相关的WWDC视频之后)。如果您对代码不感兴趣,可以在此处查看



总览



创建自定义工具的最简单方法是使用os_signpost,这正是我们在这里要做的。您可以使用它来记录.event.begin.end路标指针。然后设置一个自定义工具来分析这些os_signpost间隔并提取登录到它的其他值,设置如何在图形上显示它们,如何对其进行分组,要过滤的值以及如何在该工具的详细信息窗格中显示列表或树/流程图的结构。 ...



我们想要创建一个工具,以间隔(开始+结束)的形式显示通过Web库传递的所有HTTP请求,以便我们可以看到它们花费了多长时间,并将它们与应用程序中发生的其他事件相关联。对于本文,我将Alamofire用作该工具的网络库,并将Wordpress用作我的性能分析应用程序,只是因为它们是开源的。但是,您可以轻松地将所有代码修改为您的网络库。



步骤0:检出Instruments App



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




1: signpost-



我们要在路标上构建工具,也就是说,我们将通过路标记录数据。每当请求开始或结束时,Alamofire都会发送一个Notification,所以我们所需要做的就是这样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)
}




当请求开始时,我们记录路标.begin,当请求完成时,我们添加路标.end。为了使呼叫结束与相应的呼叫开始相匹配signpostId,可以确保如果并行发生多个请求,我们可以关闭正确的时间间隔。理想情况下,我们应该存储signpostId在请求对象中,以确保我们为.begin使用相同的对象.end。但是,我不想Request在Alamofire中编辑类型,所以我决定使用OSSignpostID(log:, object:)ID对象并将其传递给它。我们使用基础对象URLSessionTask是因为在两种情况下它都是相同的,这使OSSignpostID(log:, object:)我们可以在多次调用它时返回相同的标识符。



我们使用格式字符串记录数据。您可能应该始终使用定义良好的字符串来分隔两个参数,以使其在工具端更容易解析,并且使解析更容易。请注意,.end如果您已将数据记录到,则无需记录该数据.begin它们将合并为一个间隔,您将可以访问它们。



步骤2:在Xcode中创建一个新的自定义工具项目。



请遵循创建自定义工具(WWDC 2018的会议410)Instruments App帮助-创建工具箱项目中的步骤以在Xcode中创建新的工具箱项目。这将为您提供一个基本的Xcode项目.instrpkg我们将在那里显示所有详细信息。



步骤3:休息



基本上,您将按照Instruments App help-从路标数据创建工具中概述的步骤进行操作尽管此处所有步骤的描述都是正确的,但它们仍然缺少很多细节,因此最好在您面前摆放一个真正的自定义工具的示例。你可以在这里看看我的基本上,您将需要以下部分:



概述



这将告诉工具如何将路标指针中的数据解析为可以使用的变量。您定义一个模板,该模板从日志消息中提取变量并将其分布在各列中。



<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是您稍后将参考此列的标识符。由于某种原因,我发现将列命名为与变量相同有点奇怪,因此我在它们前面加了前缀column。但是,据我所知,没有必要这样做。



工具



工具包含一个基本定义:



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




很简单这些字段大多数都是自由格式的文本,或者与您之前定义的材料有关(schema-ref)。但categoryicon只能有一小部分定义的值在这里这里



工具中



的图形图形定义了该工具的用户界面的图形部分,即您在跟踪区域中看到的视觉表示。看起来像这样:



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




您可以具有不同的泳道,并且可以使用plot-template来实现每个泳道的动态图数。我的示例包含一个简单图形的示例我不知道为什么graph,并lane有标题。除此之外,中的每个图表plot-template还从接收标签label-format



列表,聚合或任何用于详细视图



的工具仅使用图表,这些工具看起来就有些不完整。您还希望在“详细信息视图”中显示某些内容。你可以做到这一点listaggregationnarrative我可能还没有遇到更多选择。聚合看起来像这样:



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




列表如下所示:



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




奖金材料



实际上,这就是全部。但是,您所做的除了WWDC视频所描述的以外,还做得很多,我保证会填补一些空白。



我的示例工具包含一些其他方面。



一个小的CLIPS表达式,根据请求是否成功为间隔着色。您可以在《仪器工程类型参考》中找到颜色值

使用图表模板,您可以在一个条上显示多个图表,或者,例如,在我的示例中,每个主机都有一个图表。但是,您可以具有多个层次结构,并允许用户展开或折叠零件。为此,您将需要使用一个元素<engineering-type-track>定义层次结构,然后层次结构的不同级别添加(增强)添加图形详细视图另外,不要忘记激活相应工具中的附加组件



进一步行动



如果您还没有从以前的链接中找到它,那么实际上可以为您提供文件中所有内容的完整帮助.instrpkg。例如,它将告诉您可以为工具选择的元素 <instrument>图标。重要一点:顺序很重要。因此,例如,在<instrument><title>必须早于出现<category>,否则描述将是无效的。再次



查看自定义工具创建(WWDC 2018的Session 410),以记下您可能需要的详细信息。WWDC 2019会话也有示例代码,我在其中找到了使用示例<engineering-type-track>



CLIPS是用于编写自定义建模器的语言(建模器-在此不做介绍),但是它也可以用于列声明期间的短表达式。语言文档远远超出了您的需求。要编写一个简单的表达式,您可能需要了解的主要内容是CLIPS使用前缀表示法,因此?a + ?b您必须编写(+ ?a ?b)



有关自定义工具的更多文章



在Xcode中创建自定义工具箱的 Igor



调试



将工具添加XCodeos_signpost到跟踪文档中总是一个好主意这样,如果某些事情无法按预期工作,则可以检查数据是否正确记录以及工具是否正确解释了数据。



我还没有想到的



  • 默认情况下,如何使用Instruments为您提供的值并在用户界面(例如,持续时间)中显示列定义的表达式(例如,通过将接收的字节除以持续时间来创建波特率列)。
  • 如何在额外的细节区域中显示任何内容。感觉就像是只用于调用堆栈。例如,我想显示所选请求的JSON正文,但没有找到任何可以阐明这一点的示例。




这个工具能做什么



仍在进行中



下载它并自己查看。



脚注



  1. 好的,实际上还有其他原因。
  2. 在我的示例中,完整的登录代码位于Logger.swift文件中Alamofire 4.8假定使用该功能,因为这是当前版本的Wordpress iOS应用使用的格式,尽管在撰写本文时已经发布了Alamofire 5。由于有了这些通知,可以在不更改Alamofire本身的情况下轻松添加此日志记录代码,但是,如果您具有自定义网络库,则可以更轻松地向该库本身添加条目以访问更多详细信息。





iOS开发快速入门(免费网络研讨会)






阅读更多






All Articles