📄
Henry
  • Henry的神秘小屋
  • 其他
    • 字节码与机器码的区别?
  • ObjectiveC
    • NSInvocation
    • 检测OC项目中未使用的方法
    • Method Selector
    • 消息转发
  • Swift
    • 检测Swift项目中未使用的类 方法 属性
    • NSCoding
    • Mirror
    • JSONEncode JSONDecode
    • Any AnyObject AnyClass
    • is as? as! as
  • Cocoapods
    • 看懂Podfile.lock文件
    • 在编写的Pod中使用宏预编译
  • iOS
    • 苹果应用 保持画面流畅
    • 关于计算机展示图像的一些问题
    • @testable
    • iOS中URLRequest的缓存策略
    • CodePush接入流程
    • H5在WKWebView中读取沙盒文件
    • FDLog App客户端日志系统
    • 如何实现JSBridge基于WKWebView
    • 网络请求各个指标的度量
    • iOS13 UIModalPresentationStyle
    • 实现H5离线包机制
    • NSURLProtocol 拦截器
    • Framework
    • Lock
    • CFNetwork NSURLSession NSURLConnection
    • setNeedsLayout layoutIfNeeded layoutSubviews
    • StackView
    • Flutter Method Channel:从Dart到Native调用链
    • JSONSerialization.ReadingOptions
    • JSONSerialization.WritingOptions
    • RunLoop高级2
    • RunLoop高级1
    • RunLoop中级
    • RunLoop初级
    • LineBreak AutoShrink
    • 如何给H5出WebView调试包
    • TODO、FIXME、!!!、???、MARK
    • Operation的使用
    • UserDefault
    • 了解WKWebView
    • 输出日志信息到系统控制台
    • Float Double 失去精度问题
    • 使用xcodebuild命令打包导出
    • 在iOS项目中使用C++定义的模块
    • 证书问题
    • 创建常驻线程
  • 源码
    • 阅读PINCache源码
    • 解读AspectHook
    • HandyJSON是如何实现的?
  • 汇编
    • 看懂汇编
由 GitBook 提供支持
在本页
  • 内容
  • 一,效果
  • 二,一共有哪些指标?
  • 三,如何得到这些指标?
  • 四,总结

这有帮助吗?

  1. iOS

网络请求各个指标的度量

上一页如何实现JSBridge基于WKWebView下一页iOS13 UIModalPresentationStyle

最后更新于5年前

这有帮助吗?

Demo:

2016 - 2019 WWDC 介绍这块的 PPT

内容

  • 效果

  • 一共有哪些指标?

  • 如何得到这些指标?

  • 总结

一,效果

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

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

二,一共有哪些指标?

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

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

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

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

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

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

三,如何得到这些指标?

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

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

NSURLSession *session = [NSURLSession  
 sessionWithConfiguration: config  
 delegate:self delegateQueue:nil];
  1. 其次让 self 遵守 NSURLSessionTaskDelegate 协议 实现如下代理方法

- (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,但是实际测试结果并不是这样。

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

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

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

httpmetrics.png
WechatIMG1009.png

我也弄不清楚为什么,将这个问题放在了 stackoverflow 上面: 如果有知道的小伙伴告诉我下哈,谢谢啦。

Demo:

https://stackoverflow.com/questions/59350261/ios-why-is-requestenddate-earlier-than-requeststartdate
https://github.com/zColdWater/iOSURLMetricsDemo/tree/master
https://github.com/zColdWater/iOSURLMetricsDemo/tree/master
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