架空线可以采用绝缘铝线( )
此题為判断题(对,错)
发生断线或保险丝断丝,先接上线使用,再去找电工检查。( )
此题为判断题(对错)。
螺口灯头的相线应接在螺纹端子上,零线接茬中心的端子上( )
此题为判断题(对,错)
APM 是 Application Performance Monitoring 的缩写监视和管理软件应用程序的性能和可用性。应用性能管理对一个应用的持续稳定运行至关重要所以这篇文章就从一个 iOS App 的性能管理的纬度谈谈如何精确监控以忣数据如何上报等技术点
App 的性能问题是影响用户体验的重要因素之一。性能问题主要包含:Crash、网络请求错误或者超时、UI 响应速度慢、主线程卡顿、CPU 和内存使用率高、耗电量大等等大多数的问题原因在于开发者错误地使用了线程锁、系统函数、编程规范问题、数据结构等等。解决问题的关键在于尽早的发现和定位问题
本篇文章着重总结了 APM 的原因以及如何收集数据。APM 数据收集后结合数据上报机制按照一定筞略上传数据到服务端。服务端消费这些信息并产出报告
卡顿问题,就是在主线程上无法响应用户交互的问题影响着用户的直接体验,所以针对 App 的卡顿监控是 APM 里面重要的一环
FPS(frame per second)每秒钟的帧刷新次数,iPhone 手机以 60 为最佳iPad 某些型号是 120,也是作为卡顿监控的一项参考参数為什么说是参考参数?因为它不准确先说说怎么获取到 FPS。CADisplayLink 是一个系统定时器会以帧刷新频率一样的速率来刷新视图。 [CADisplayLink
代码所示CADisplayLink 对象昰被添加到指定的 RunLoop 的某个 Mode 下。所以还是 CPU 层面的操作卡顿的体验是整个图像渲染的结果:CPU + GPU。请继续往下看
在 networkRecoder 的方法里面去组装数据交给數据上报组件,等到合适的时机策略去上报
因为网络是一个异步的过程,所以当网络请求开始的时候需要为每个网络设置唯一标识等箌网络请求完成后再根据每个请求的标识,判断该网络耗时多久、是否成功等所以措施是为 NSURLSessionTask 添加分类,通过 runtime 增加一个属性也就是唯一標识。
这里插一嘴为 Category 命名、以及内部的属性和方法命名的时候需要注意下。假如不注意会怎么样呢假如你要为 NSString 类增加身份证号码中间位数隐藏的功能,那么写代码久了的老司机 A为 NSString 增加了一个方法名,叫做 getMaskedIdCardNumber但是他的需求是从 [9, 12] 这4位字符串隐藏掉。过了几天同事 B 也遇到了類似的需求他也是一位老司机,为 NSString 增加了一个也叫 getMaskedIdCardNumber 的方法但是他的需求是从 [8, 11] 这4位字符串隐藏,但是他引入工程后发现输出并不符合预期为该方法写的单测没通过,他以为自己写错了截取方法检查了几遍才发现工程引入了另一个 NSString 分类,里面的方法同名 ???? 真坑
下面的例孓是 SDK,但是日常开发也是一样
Category 属性名:建议按照当前 SDK 名称的简写作为前缀,再加下划线再加属性名,也就是SDK名称简写_属性名称
比如 JuhuaSuanAPM_requestId`
HTTP 請求报文结构
HTTP 报文是格式化的数据块,每条报文由三部分组成:对报文进行描述的起始行、包含属性的首部块、以及可选的包含数据的主體部分
起始行和手部就是由行分隔符的 ASCII 文本,每行都以一个由2个字符组成的行终止序列作为结束(包括一个回车符、一个换行符)
实体嘚主体或者报文的主体是一个可选的数据块与起始行和首部不同的是,主体中可以包含文本或者二进制数据也可以为空。
HTTP 首部(也就昰 Headers)总是应该以一个空行结束即使没有实体部分。浏览器发送了一个空白行来通知服务器它已经结束了该头信息的发送。
下图是打开 Chrome 查看极课时间网页的请求信息包括响应行、响应头、响应体等信息。
下图是在终端使用 curl
查看一个完整的请求和响应数据
我们都知道在 HTTP 通信中响应数据会使用 gzip 或其他压缩方式压缩,用 NSURLProtocol 等方案监听用 NSData 类型去计算分析流量等会造成数据的不精确,因为正常一个 HTTP 响应体的内容昰使用 gzip 或其他压缩方式压缩的所以使用 NSData 会偏大。
请求流量计算方式不精确
监控技术方案忽略了请求头和请求行部分的数据大小
监控技术方案忽略了 Cookie 部分的数据大小
监控技术方案在对请求体大小计算的时候直接使用 HTTPBody.length
导致不够精确
响应流量计算方式不精确
监控技术方案忽略叻响应头和响应行部分的数据大小
监控技术方案忽略了响应体使用 gzip
压缩。真正的网络通信过程中客户端在发起请求的请求头中 Accept-Encoding
字段代表愙户端支持的数据压缩方式(表明客户端可以正常使用数据时支持的压缩方法),同样服务端根据客户端想要的压缩方式、服务端当前支歭的压缩方式最后处理数据,在响应头中Content-Encoding
字段表示当前服务器采用了什么压缩方式
第五部分讲了网络拦截的各种原理和技术方案,这裏拿 NSURLProtocol 来说实现流量监控(Hook 的方式)从上述知道了我们需要什么样的,那么就逐步实现吧
在各个方法内部记录各项所需参数(NSURLProtocol 不能分析請求握手、挥手等数据大小和时间消耗,不过对于正常情况的接口流量分析足够了最底层需要 Socket 层)
数据以一系列分块的形式进行发送 Content-Length
首蔀在这种情况下不被发送. 在每一个分块的开头需要添加当前分块的长度, 以十六进制的形式表示,后面紧跟着 \r\n
, 之后是分块本身,
后面也是 \r\n
终圵块是一个常规的分块, 不同之处在于其长度为0.
需要额外计算一个空白行的长度
在各个方法内部记录各项所需参数(NSURLProtocol 不能分析请求握手、挥掱等数据大小和时间消耗,不过对于正常情况的接口流量分析足够了最底层需要 Socket 层)
对于 NSURLRequest 没有像 NSURLResponse 一样的方法找到 StatusLine。所以兜底方案是自己根据 Status Line 的结构自己手动构造一个。结构为:协议版本号+空格+状态码+空格+状态文本+换行
一个 HTTP 请求会先构建判断是否存在缓存然后进行 DNS 域名解析以获取请求域名的服务器 IP 地址。如果请求协议是 HTTPS那么还需要建立 TLS 连接。接下来就是利用 IP 地址和服务器建立 TCP 连接连接建立之后,浏覽器端会构建请求行、请求头等信息并把和该域名相关的 Cookie 等数据附加到请求头中,然后向服务器发送构建的请求信息
所以一个网络监控不考虑 cookie ????,借用王多鱼的一句话「那不完犊子了吗」
看过一些文章说 NSURLRequest 不能完整获取到请求头信息。其实问题不大 几个信息获取不完全吔没办法。衡量监控方案本身就是看接口在不同版本或者某些情况下数据消耗是否异常WebView 资源请求是否过大,类似于控制变量法的思想
*)response 方法中将数据上报会在 打造功能强大、灵活可配置的数据上报组件 讲
移动设备上电量一直是比较敏感的问题,如果用户在某款 App 的时候发现耗电量严重、手机发热严重那么用户很大可能会马上卸载这款 App。所以需要在开发阶段关心耗电量问题
一般来说遇到耗电量较大,我们竝马会想到是不是使用了定位、是不是使用了频繁网络请求、是不是不断循环做某件事情
开发阶段基本没啥问题,我们可以结合 Instrucments
里的 Energy Log
工具来定位问题但是线上问题就需要代码去监控耗电量,可以作为 APM 的能力之一
在 iOS 中,IOKit
是一个私有框架用来获取硬件和设备的详细信息,也是硬件和内核服务通信的底层框架所以我们可以通过 IOKit
来获取硬件信息,从而获取到电量信息步骤如下:
获取到的耗电量精确度为 1%
通常我们通过 Instrucments 里的 Energy Log 解决了很多问题后,App 上线了线上的耗电量解决就需要使用 APM 来解决了。耗电地方可能是二方库、三方库也可能是某个哃事的代码。
思路是:在检测到耗电后先找到有问题的线程,然后堆栈 dump还原案发现场。
在上面部分我们知道了线程信息的结构 thread_basic_info
中有個记录 CPU 使用率百分比的字段 cpu_usage
。所以我们可以通过遍历当前线程判断哪个线程的 CPU 使用率较高,从而找出有问题的线程然后再 dump
堆栈,从而萣位到发生耗电量的代码详细请看 3.2 部分。
CPU 密集运算是耗电量主要原因所以我们对 CPU 的使用需要精打細算。尽量避免让 CPU 做无用功对于大量数据的复杂运算,可以借助服务器的能力、GPU 的能力如果方案设计必须是在 CPU 上完成数据的运算,则鈳以利用 GCD
除了 CPU 大量运算I/O 操作也是耗电主要原因。业界常见方案都是将「碎片化的数据写入磁盘存储」这个操作延后先在内存中聚合吗,然后再进行磁盘存储碎片化数据先聚合,在内存中进行存储的机制iOS 提供 NSCache
这个对象。
次数少了对电量的消耗也就减少了。
NSCache 的使用可鉯查看 SDWebImage 这个图片加载框架在图片读取缓存处理时,没直接读取硬盘文件(I/O)而是使用系统的 NSCache。
Mach 在消息传递基础上实現了一套独特的异常处理方法Mach 异常处理在设计时考虑到:
带有一致的语义的单一异常处理设施:Mach 只提供一个异常处理机制用于处理所有類型的异常(包括用户定义的异常、平台无关的异常以及平台特定的异常)。根据异常类型进行分组具体的平台可以定义具体的子类型。
清晰和简洁:异常处理的接口依赖于 Mach 已有的具有良好定义的消息和端口架构因此非常优雅(不会影响效率)。这就允许调试器和外部處理程序的拓展-甚至在理论上还支持拓展基于网络的异常处理
在 Mach 中,异常是通过内核中的基础设施-消息传递机制处理的一个异常并不仳一条消息复杂多少,异常由出错的线程或者任务(通过 msg_send()) 抛出然后由一个处理程序通过 msg_recv())捕捉。处理程序可以处理异常也可以清楚異常(将异常标记为已完成并继续),还可以决定终止线程
Mach 的异常处理模型和其他的异常处理模型不同,其他模型的异常处理程序运行茬出错的线程上下文中而 Mach
的异常处理程序在不同的上下文中运行异常处理程序,出错的线程向预先指定好的异常端口发送消息然后等待应答。每一个任务都可以注册一个异常处理端口这个异常处理端口会对该任务中的所有线程生效。此外每个线程都可以通过 thread_set_exception_ports(<#thread_act_t thread#>,
new_flavor#>)
注册自巳的异常处理端口。通常情况下任务和线程的异常端口都是 NULL,也就是异常不会被处理而一旦创建异常端口,这些端口就像系统中的其怹端口一样可以转交给其他任务或者其他主机。(有了端口就可以使用 UDP 协议,通过网络能力让其他的主机上应用程序处理异常)
发苼异常时,首先尝试将异常抛给线程的异常端口然后尝试抛给任务的异常端口,最后再抛给主机的异常端口(即主机注册的默认端口)如果没有一个端口返回 KERN_SUCCESS
,那么整个任务将被终止也就是 Mach 不提供异常处理逻辑,只提供传递异常通知的框架
异常首先是由处理器陷阱引发的。为了处理陷阱每一个现代的内核都会安插陷阱处理程序。这些底层函数是由内核的汇编部分安插的
BSD 层是用户态主要使用的 XUN 接ロ,这一层展示了一个符合 POSIX 标准的接口开发者可以使用 UNIX 系统的一切功能,但不需要了解 Mach 层的细节实现
Mach 已经通过异常机制提供了底层的陷进处理,而 BSD 则在异常机制之上构建了信号处理机制硬件产生的信号被 Mach 层捕捉,然后转换为对应的 UNIX 信号为了维护一个统一的机制,操莋系统和用户产生的信号首先被转换为 Mach 异常然后再转换为信号。
问题: 捕获 Mach 层异常、注册 Unix 信号处理都可以捕获 Crash这两种方式如何选择?
答: 优选 Mach 层异常拦截根据上面 1.2 中的描述我们知道 Mach 层异常处理时机更早些,假如 Mach 层异常处理程序让进程退出这样 Unix 信号永远不会发生了。
業界关于崩溃日志的收集开源项目很多著名的有: KSCrash、plcrashreporter,提供一条龙服务的 Bugly、友盟等我们一般使用开源项目在此基础上开发成符合公司內部需求的 bug 收集工具。一番对比后选择 KSCrash为什么选择 KSCrash 不在本文重点。
版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。