> For the complete documentation index, see [llms.txt](https://yongpenglovemimi123.gitbook.io/henry/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yongpenglovemimi123.gitbook.io/henry/ios/wang-luo-qing-qiu-ge-ge-zhi-biao-de-du-liang.md).

# 网络请求各个指标的度量

![](https://img.hacpai.com/bing/20190627.jpg?imageView2/1/w/960/h/540/interlace/1/q/100)

> Demo: <https://github.com/zColdWater/iOSURLMetricsDemo/tree/master>
>
> 2016 - 2019 WWDC 介绍这块的 PPT
>
> <https://github.com/zColdWater/Resources/blob/master/Images/711_nsurlsession_new_features_and_best_practices.pdf>
>
> <https://github.com/zColdWater/Resources/blob/master/Images/713_advances_in_networking_part_2.pdf>

## 内容

* 效果
* 一共有哪些指标？
* 如何得到这些指标？
* 总结

## 一，效果

比如我们向 `https://github.com/` 发起一个 http 的请求，我们看下可以拿到哪些指标。

通过下面的这些指标，我们可以分析这些数据，得知我们的网络请求为什么比较慢，哪个阶段比较慢，再逐步优化。

![httpmetrics.png](https://img.hacpai.com/file/2019/12/httpmetrics-6315f371.png)

## 二，一共有哪些指标？

下面这个类，在 >= iOS10 的操作系统才可以运作，部分属性在 >= iOS13 才可以使用，我分别解释下他们的意思，但是 有些属性，我是真的不知道是干什么的，经过尝试，没有变化，这里我就不做解释。

如果觉得我是在这瞎 BB，可以去 navigate to NSURLSessionTaskTransactionMetrics define 自己查看，上面备注写的也很清楚了。

```objectivec
API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0))
@interface NSURLSessionTaskTransactionMetrics : NSObject

// URLRequest 请求的Body和Header都在这里面呢，包含请求的全部信息。
@property (copy, readonly) NSURLRequest *request;

// URLResponse 相应的信息都在这个对象里面
@property (nullable, copy, readonly) NSURLResponse *response;

// 请求任务开始前，可以理解为整个请求的最开始的时间点。
@property (nullable, copy, readonly) NSDate *fetchStartDate;

// 开始DNS解析的时间点
@property (nullable, copy, readonly) NSDate *domainLookupStartDate;

// 结束DNS解析的时间点
@property (nullable, copy, readonly) NSDate *domainLookupEndDate;

// 建立连接的起始点
@property (nullable, copy, readonly) NSDate *connectStartDate;

// 建立 SSL/TLS 对话密钥的起始时间
@property (nullable, copy, readonly) NSDate *secureConnectionStartDate;

// 建立 SSL/TLS 对话密钥的终点时间
@property (nullable, copy, readonly) NSDate *secureConnectionEndDate;

// 建立连接的终点
@property (nullable, copy, readonly) NSDate *connectEndDate;

// 建立好连接通道后，请求开始的时间点
@property (nullable, copy, readonly) NSDate *requestStartDate;

// 建立好连接通道后，请求结束的时间点
@property (nullable, copy, readonly) NSDate *requestEndDate;

// 开始得到响应的时间点
@property (nullable, copy, readonly) NSDate *responseStartDate;

// 接收完最后一字节的数据的响应结束时间点
@property (nullable, copy, readonly) NSDate *responseEndDate;

// 网络协议的名称
@property (nullable, copy, readonly) NSString *networkProtocolName;

// 是否使用了网络代理
@property (assign, readonly, getter=isProxyConnection) BOOL proxyConnection;

// 是否重用了连接
@property (assign, readonly, getter=isReusedConnection) BOOL reusedConnection;

// 资源获取的类型，有缓存，Network，不知道，等
@property (assign, readonly) NSURLSessionTaskMetricsResourceFetchType resourceFetchType;

// iOS13+ 发送的请求头占的字节
@property (readonly) int64_t countOfRequestHeaderBytesSent API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));

// iOS13+ 发送的Body占的字节
@property (readonly) int64_t countOfRequestBodyBytesSent API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));

// iOS13+ 发送的Body在Encoding之前占的字节
@property (readonly) int64_t countOfRequestBodyBytesBeforeEncoding API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));

// iOS13+ 响应头占字节数
@property (readonly) int64_t countOfResponseHeaderBytesReceived API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));

// iOS13+ 响应Body体占字节数
@property (readonly) int64_t countOfResponseBodyBytesReceived API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));

// iOS13+ Decoding之后响应消息体占字节数
@property (readonly) int64_t countOfResponseBodyBytesAfterDecoding API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));

// iOS13+ 本地IP地址
@property (nullable, copy, readonly) NSString *localAddress API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));

// iOS13+ 本地端口
@property (nullable, copy, readonly) NSNumber *localPort API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));

// iOS13+ 访问服务器机器的IP地址
@property (nullable, copy, readonly) NSString *remoteAddress API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));

// iOS13+ 访问服务器机器的端口号
@property (nullable, copy, readonly) NSNumber *remotePort API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));

// iOS13+ TLS协议的版本 
@property (nullable, copy, readonly) NSNumber *negotiatedTLSProtocolVersion API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));

// iOS13+ TLS选择的加密协议
@property (nullable, copy, readonly) NSNumber *negotiatedTLSCipherSuite API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));

// iOS13+ 不知道，我不论怎么切换网络环境一直都是 NO
@property (readonly, getter=isCellular) BOOL cellular API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));

// iOS13+ 不知道，我不论怎么切换网络环境一直都是 NO
@property (readonly, getter=isExpensive) BOOL expensive API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));

// iOS13+ 不知道，我不论怎么切换网络环境一直都是 NO
@property (readonly, getter=isConstrained) BOOL constrained API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));

// iOS13+ 不知道，我不论怎么切换网络环境一直都是 NO
@property (readonly, getter=isMultipath) BOOL multipath API_AVAILABLE(macos(10.15), ios(13.0), watchos(6.0), tvos(13.0));
```

**兄弟别慌，我刚开始看也懵逼，我给你看一张图，你就不懵逼了。在下方：**

红字： 描述请求的整个过程。 白字： 对应上面的属性名字，各个阶段的位置。

是不是懵逼症状好一些了。

![WechatIMG1009.png](https://img.hacpai.com/file/2019/12/WechatIMG1009-e5b4e40f.png)

## 三，如何得到这些指标？

这里还是要感谢苹果爸爸，虽然 iOS10 才给出来，但是现在已经 9102 年了，以支持最近 3 个版本的原则，我们的用户也应该都是 iOS10+ 的了，所以这个官方提供的功能，用的安全放心，还简单，不用去 hook 这，hook 那，然后得到的数据还不准，还让工程复杂化。

> 1. 首先 调用 session 的构造器的时候，传入 delegate，这里比如就是 self。

```objectivec
NSURLSession *session = [NSURLSession  
 sessionWithConfiguration: config  
 delegate:self delegateQueue:nil];
```

> 1. 其次让 self 遵守 NSURLSessionTaskDelegate 协议 实现如下代理方法

```objectivec
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics {

    if ([metrics.transactionMetrics count] > 0) {
        [metrics.transactionMetrics enumerateObjectsUsingBlock:^(NSURLSessionTaskTransactionMetrics *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
            if (obj.resourceFetchType == NSURLSessionTaskMetricsResourceFetchTypeNetworkLoad) {

                NSLog(@"========================================================================");

                if (obj.domainLookupStartDate && obj.domainLookupEndDate) {
                    int dnsLookupTime = ceil([obj.domainLookupEndDate timeIntervalSinceDate:obj.domainLookupStartDate] * 1000);
                    NSLog(@"DNS解析时长 单位ms:%d",dnsLookupTime);
                }

                if (obj.connectStartDate && obj.connectEndDate) {
                    int tcpTime = ceil([obj.connectEndDate timeIntervalSinceDate:obj.connectStartDate] * 1000);
                    NSLog(@"TCP+SSL整个连接建立的时间 单位ms:%d",tcpTime);
                }

                if (obj.secureConnectionEndDate && obj.secureConnectionStartDate) {
                   int sslTime = ceil([obj.secureConnectionEndDate timeIntervalSinceDate:obj.secureConnectionStartDate] * 1000);
                    NSLog(@"SSL四次握手时长 单位ms:%d",sslTime);
                }

                if (obj.requestStartDate && obj.responseEndDate) {
                    int transmissionTime = ceil([obj.responseEndDate timeIntervalSinceDate:obj.requestStartDate] * 1000);
                    NSLog(@"传输时间 单位ms:%d",transmissionTime);
                }

                if (obj.fetchStartDate && obj.responseEndDate) {
                    int requestTime = ceil([obj.responseEndDate timeIntervalSinceDate:obj.fetchStartDate] * 1000);
                    NSLog(@"完整请求时长 单位ms:%d",requestTime);
                }

                if (obj.fetchStartDate) {
                    UInt64 requestDate = [obj.fetchStartDate timeIntervalSince1970] * 1000;
                    NSLog(@"开始发起请求的时间点:%llu",requestDate);
                }

                if (obj.responseEndDate) {
                    UInt64 responseEndDate = [obj.responseEndDate timeIntervalSince1970] * 1000;
                    NSLog(@"请求结束的时间点:%llu",responseEndDate);
                }

                NSLog(@"HTTP请求类型:%@",obj.request.HTTPMethod);
                NSLog(@"协议名称:%@",obj.networkProtocolName);
                NSLog(@"请求URL:%@",[obj.request.URL absoluteString]);
                NSLog(@"是否使用代理:%d",obj.isProxyConnection);
                NSLog(@"是否重用连接获取资源:%d",obj.reusedConnection);


                // iOS13 或者 iOS13 以上才可以使用
                NSLog(@"服务器IP:%@",obj.remoteAddress);
                NSLog(@"服务器端口:%@",obj.remotePort);
                NSLog(@"请求的Header字节数:%lld bytes",obj.countOfRequestHeaderBytesSent);
                NSLog(@"请求的Body字节数:%lld bytes",obj.countOfRequestBodyBytesSent);
                NSLog(@"请求的Encoding之前的Body字节数:%lld bytes",obj.countOfRequestBodyBytesBeforeEncoding);
                NSLog(@"响应的Header字节数:%lld bytes",obj.countOfResponseHeaderBytesReceived);
                NSLog(@"响应的Body字节数:%lld bytes",obj.countOfResponseBodyBytesReceived);
                NSLog(@"响应的Decoding之后的Body字节数:%lld bytes",obj.countOfResponseBodyBytesAfterDecoding);


                // 网络类型
//                NSURLSessionTaskMetricsResourceFetchTypeUnknown
//                NSURLSessionTaskMetricsResourceFetchTypeNetworkLoad
//                NSURLSessionTaskMetricsResourceFetchTypeServerPush
//                NSURLSessionTaskMetricsResourceFetchTypeLocalCache

            }
        }];
    }
}
```

> 1. Run 一下，看看 delegate 方法都捕捉到了哪些指标参数 。

## 四，总结

但是我发现一个问题，requestStartDate 比 requestEndDate 晚， 例如打印如下： requestStartDate:1576467073.038733 requestEndDate:1576467071.590589 按照 WWDC 上面所描述的，应该 start 先于 end，但是实际测试结果并不是这样。

我也弄不清楚为什么，将这个问题放在了 stackoverflow 上面： <https://stackoverflow.com/questions/59350261/ios-why-is-requestenddate-earlier-than-requeststartdate> 如果有知道的小伙伴告诉我下哈，谢谢啦。

补： 我自己回答了我的提问，在 OC 上面测试，我发现结果是正确的，但是在 swift 却是有问题的。

了解了一个完整的网路请求链路，以及每个链路的时间点，都是做什么，如何使用官方提供的 API 来进行收集数据，进行性能分析。

如果觉得自己写 Demo 测试验证麻烦，直接下载我上传的 Demo 没毒放心

Demo: <https://github.com/zColdWater/iOSURLMetricsDemo/tree/master>
