主要方案为使用apple在 iOS 10 推出一个API:
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
其中该代理中的metrics参数类中包含了如下数据:
@interface NSURLSessionTaskMetrics : NSObject
/*
* 数组包含为任务执行期间创建的每个请求/响应事务收集的对应数据。
*/
@property (copy, readonly) NSArray<NSURLSessionTaskTransactionMetrics *> *transactionMetrics;
/*
* 从任务创建时间到任务完成时间的间隔。
* 任务创建时间是实例化任务的时间。
* 任务完成时间是指任务即将将其内部状态更改为已完成的时间。
*/
@property (copy, readonly) NSDateInterval *taskInterval;
/*
* 重定向次数
*/
@property (assign, readonly) NSUInteger redirectCount;
@end
而其中包含的每次请求/响应的类是我们此次关注的重点,该类包含了如下属性:
各个环节示意
字段 | 解释 |
---|---|
secureConnectionEndDate | 如果使用了加密连接,那么secureConnectionEndDate是安全握手完成之后的结束时间 |
connectEndDate | 用户代理完成与服务器的连接建立之后(包括与安全相关的握手和其他握手完成)的结束时间 |
connectStartDate | 用户代理开始与服务器建立连接开始的时间 |
secureConnectionStartDate | 用户代理立即开始TLS握手之前的开始时间 |
requestStartDate | 用户代理立即开始请求源之前的开始时间,无论是从服务器还是从本地资源检索资源 |
fetchStartDate | 返回用户代理开始获取资源的时间,无论该资源是从服务器还是从本地资源中检索到的。但是需要注意:如果使用了持久连接或从本地资源中检索了资源,则domainLookupStartDate、connectEndDate、domainLookupEndDate、connectStartDate、secureConnectionStartDate、secureConnectionEndDate指标将设置为nil |
domainLookupStartDate | 返回用户代理立即开始资源查找之前的时间,即DNS开始时间 |
domainLookupEndDate | 返回资源查找完成后的时间,即DNS结束时间 |
而根据整个网络请求链路,我们新增记录了几个时间点:
字段 | 解释 |
---|---|
addToQueueStartDate | 网络请求任务被添加到队列里的时间节点 |
requestFailedEndDate | 网络请求失败的结束时间节点 |
而以上这些数据恰恰帮我们完成了对应阶段耗时操作的统计过程:
字段 | 解释 | 计算方式 |
---|---|---|
transmitTime | 包含request和response时间 | responseEndDate与requestStartDate的时间差 |
queueTime | 排队时间 | 使用fetchStartDate与自定义addToQueueStartDate的时间差 |
dnsTime | DNS解析耗时,需注意可能为0,例如使用了持久连接或从本地资源中检索了资源,则并不会重新进行DNS过程 | domainLookupEndDate与domainLookupStartDate的时间差 |
connectTime | 包含tcp和ssl连接时间,需注意可能为0,例如使用了持久连接或从本地资源中检索了资源,则并不会重新进行DNS过程 | secureConnectionEndDate与secureConnectionStartDate的时间差 |
connectionReleaseTime(已去除) | 整个http网络连接耗时 | connectEndDate与connectStartDate的时间差 |
callTime | 整个网络请求过程的耗时 total | 直接采用NSURLSessionTaskMetrics类中的taskInterval参数 |
callFailedTime | 与callEndTime对应 只不过请求失败了,此时是由于请求超时错误,与connectFailedTime所代表的的请求出现的异常不同 | requestFailedEndDate与fetchStartDate的时间差 |
具体过程请参照下图:
实现流程图