公司法
当前位置: 首页 法律大全

流量监控怎么设置(iOS中如何流量监控分析)

时间:2023-08-10 作者: 小编 阅读量: 1 栏目名: 法律大全

iOS中如何流量监控分析由于骑手不能随时处在有WIFI的状态,流量变成了很敏感的问题,为了精确到每个API的流量,进行针对性的优化,开始在我们的APM中添加流量监控功能本文将记录自己做流量监控方面的总结其中包括了非常多的踩坑。

由于骑手不能随时处在有 WIFI 的状态,流量变成了很敏感的问题,为了精确到每个 API 的流量,进行针对性的优化,开始在我们的 APM 中添加流量监控功能。本文将记录自己做流量监控方面的总结。其中包括了非常多的踩坑经验,和现有一些方案的缺陷分析,对我来说是一个非常有意义的过程。

干货预警请做好读大量代码的准备

一、资料收集

就目前来说,各家大厂基本都有自己的 APM(包括我们公司其实之前也有一套 APM,但是由于各个事业部的需求不同,尚不能完全满足物流平台的需要)但各家大厂目前开源的 APM 项目却不多,当然也可能是由于各家的业务场景差异比较大且对数据的后续处理不同。所以本次在查阅资料阶段,没有太多的源码可选参考,但有不少文章。以下是一些本次开发过程中参考的文章和开源库:

iOS-Monitor-Platform[1]• GodEye[2]• NetworkEye[3]• 移动端性能监控方案Hertz[4]• 使用NSURLProtocol注意的一些问题[5]• iOS 开发中使用 NSURLProtocol 拦截 HTTP 请求[6]• 获取NSURLResponse的HTTPVersion[7]

但以上这些资料对我们的需求都有不足之处:

1. Request 和 Response 记在同一条记录

在实际的网络请求中 Request 和 Response 不一定是成对的,如果网络断开、或者突然关闭进程,都会导致不成对现象,如果将 Request 和 Response 记录在同一条数据,将会对统计造成偏差

2. 上行流量记录不精准

主要的原因有三大类:

直接忽略了 Header 和 Line 部分忽略了 Cookie 部分,实际上,臃肿的 Cookie 也是消耗流量的一部分• body 部分的字节大小计算直接使用了 HTTPBody.length 不够准确

3. 下行流量记录不精准

主要原因有:

直接忽略了 Header 和 Status-Line 部分• body 部分的字节大小计算直接使用了 expectedContentLength 不够准确• 忽略了 gzip 压缩,在实际网络编程中,往往都使用了 gzip 来进行数据压缩,而系统提供的一些监听方法,返回的 NSData 实际是解压过的,如果直接统计字节数会造成大量偏差

后文将详细讲述。

二、需求

先简单罗列我们的需求:

• Request 基本信息记录• 上行流量• Reponse 基本信息记录• 下行流量• 数据归类:按照 host 和 path 归类,一条记录记载改 host/path 的 Request 记录数,Response 记录数,Reqeust 总流量(上行流量),Reponse 总流量(下行流量)

我们的侧重点是流量统计,为了方便分析 APP 使用中哪些 API 消耗流量多。所以对上行、下行流量都需要尽量准确记录。

最终的数据库表展示:

type 字段表示的是『该条记录是 Request 还是 Response』,几个 length 分别记录了流量的各个细节,包括:总字节数、Line 字节数、Header 字节数、Body 字节数。

最后的界面展示类似于:

三、分析现有资料

现在分析一下上面收集到的资料有哪些不足之处。

GodEye[2] | NetworkEye[3]:

NetworkEye 是 GodEye 的一部分,可以单独拆出来使用的网络监控库。查阅两者的源码后发现,NetworkEye

1) 仅仅记录了 Reponse 的流量

2) 通过 expectedContentLength 记录是不准确的(后面将会说到)

3) 仅仅记录了总和,这对我们来说是无意义的,不能分析出哪条 API 流量使用多

移动端性能监控方案Hertz[4]:

美团的文章中展示几个代码片段:

- (void)connectionDidFinishLoading:(NSURLConnection *)connection{[self.client URLProtocolDidFinishLoading:self];self.data = nil;if (connection.originalRequest) {WMNetworkUsageDataInfo *info = [[WMNetworkUsageDataInfo alloc] init];self.connectionEndTime = [[NSDate date] timeIntervalSince1970];info.responseSize = self.responseDataLength;info.requestSize = connection.originalRequest.HTTPBody.length;info.contentType = [WMNetworkUsageURLProtocol getContentTypeByURL:connection.originalRequest.URL andMIMEType:self.MIMEType];[[WMNetworkMeter sharedInstance] setLastDataInfo:info];[[WMNetworkUsageManager sharedManager] recordNetworkUsageDataInfo:info];}

在 connectionDidFinishLoading 中记录了整个网络请求结束的时间、 response 数据大小、request 数据大小以及一些其他数据。

总体来说是比较详细的,但是这里并没有给出 self.responseDataLength 的具体逻辑,另外 connection.originalRequest.HTTPBody.length 仅仅是 Request body 的大小。

iOS-Monitor-Platform[1]:

这篇文章比较详细的介绍了整个 APM 制作的过程,贴出了很多代码段,应该说非常详细也极具参考价值。

在流量部分,也分别针对了上行流量、下行流量进行了区分,但其中:

1) 没有处理 gzip 压缩情况

2) 对 Header 计算大小的方式是 Dictionary 转 NSData,然而实际上头部并不是 Json 格式(这块我觉得很迷,因为作者特意展示了 HTTP 报文组成)

四、动手自己做

HTTP 报文

为了更好的让大家了解 HTTP 流量计算的一些关键信息,首先要了解 HTTP 报文的组成。

再来随便抓个包具体看看:

iOS 下的网络监控

这块我采用的大家耳熟能详的 NSURLProtocol,NSURLProtocol 方式除了通过 CFNetwork 发出的网络请求,全部都可以拦截到。

Apple 文档[8]中对 NSURLProtocol 有非常详细的描述和使用介绍

An abstract class that handles the loading of protocol-specific URL data.

如果想更详细的了解 NSURLProtocol,也可以看大佐的这篇文章[9]

在每一个 HTTP 请求开始时,URL 加载系统创建一个合适的 NSURLProtocol 对象处理对应的 URL 请求,而我们需要做的就是写一个继承自 NSURLProtocol 的类,并通过 - registerClass: 方法注册我们的协议类,然后 URL 加载系统就会在请求发出时使用我们创建的协议对象对该请求进行处理。

NSURLProtocol 是一个抽象类,需要做的第一步就是集成它,完成我们的自定义设置。

创建自己的 DMURLProtocol,为它添加几个属性并实现相关接口:

@interface DMURLProtocol() <NSURLConnectionDelegate, NSURLConnectionDataDelegate>@property (nonatomic, strong) NSURLConnection *connection;@property (nonatomic, strong) NSURLRequest *dm_request;@property (nonatomic, strong) NSURLResponse *dm_response;@property (nonatomic, strong) NSMutableData *dm_data;@end

canInitWithRequest & canonicalRequestForRequest:

static NSString *const DMHTTP = @"LPDHTTP";

(BOOL)canInitWithRequest:(NSURLRequest *)request {if (![request.URL.scheme isEqualToString:@"http"]) {return NO;}// 拦截过的不再拦截if ([NSURLProtocol propertyForKey:LPDHTTP inRequest:request] ) {return NO;}return YES;}

(NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {NSMutableURLRequest *mutableReqeust = [request mutableCopy];[NSURLProtocol setProperty:@YESforKey:DMHTTP inRequest:mutableReqeust];return [mutableReqeust copy];}

startLoading:

- (void)startLoading {NSURLRequest *request = [[self class] canonicalRequestForRequest:self.request];self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];self.dm_request = self.request;}

didReceiveResponse:

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];self.dm_response = response;}

didReceiveData:

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {[self.client URLProtocol:self didLoadData:data];[self.dm_data appendData:data];}

以上部分是为了在单次 HTTP 请求中记录各个所需要属性。

记录 Response 信息

前面的代码实现了在网络请求过程中为 dm_response 和 dm_data 赋值,那么在 stopLoading 方法中,就可以分析 dm_response 和 dm_data 对象,获取下行流量等相关信息。

需要说明的是,如果需要获得非常精准的流量,一般来说只有通过 Socket 层获取是最准确的,因为可以获取包括握手、挥手的数据大小。当然,我们的目的是为了分析 App 的耗流量 API,所以仅从应用层去分析也基本满足了我们的需要。

上文中说到了报文的组成,那么按照报文所需要的内容获取。

Status Line

非常遗憾的是 NSURLResponse 没有接口能直接获取报文中的 Status Line,甚至连 HTTP Version 等组成 Status Line 内容的接口也没有。

最后,我通过转换到 CFNetwork 相关类,才拿到了 Status Line 的数据,这其中可能涉及到了读取私有 API

这里我为 NSURLResponse 添加了一个扩展:NSURLResponse DoggerMonitor,并为其添加 statusLineFromCF 方法

typedef CFHTTPMessageRef (*DMURLResponseGetHTTPResponse)(CFURLRef response);- (NSString *)statusLineFromCF {NSURLResponse *response = self;NSString *statusLine = @"";// 获取CFURLResponseGetHTTPResponse的函数实现NSString *funName = @"CFURLResponseGetHTTPResponse";DMURLResponseGetHTTPResponse originURLResponseGetHTTPResponse =dlsym(RTLD_DEFAULT, [funName UTF8String]);SEL theSelector = NSSelectorFromString(@"_CFURLResponse");if ([response respondsToSelector:theSelector] &&NULL != originURLResponseGetHTTPResponse) {// 获取NSURLResponse的_CFURLResponseCFTypeRef cfResponse = CFBridgingRetain([response performSelector:theSelector]);if (NULL != cfResponse) {// 将CFURLResponseRef转化为CFHTTPMessageRefCFHTTPMessageRef messageRef = originURLResponseGetHTTPResponse(cfResponse);statusLine = (__bridge_transfer NSString *)CFHTTPMessageCopyResponseStatusLine(messageRef);CFRelease(cfResponse);}}return statusLine;}

通过调用私有 API _CFURLResponse 获得 CFTypeRef 再转换成 CFHTTPMessageRef,获取 Status Line。

再将其转换成 NSData 计算字节大小:

- (NSUInteger)dm_getLineLength {NSString *lineStr = @"";if ([self isKindOfClass:[NSHTTPURLResponse class]]) {NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)self;lineStr = [self statusLineFromCF];}NSData *lineData = [lineStr dataUsingEncoding:NSUTF8StringEncoding];return lineData.length;}

Header

通过 httpResponse.allHeaderFields 拿到 Header 字典,再拼接成报文的 key: value 格式,转换成 NSData 计算大小:

- (NSUInteger)dm_getHeadersLength {NSUInteger headersLength = 0;if ([self isKindOfClass:[NSHTTPURLResponse class]]) {NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)self;NSDictionary<NSString *, NSString *> *headerFields = httpResponse.allHeaderFields;NSString *headerStr = @"";for (NSString *key in headerFields.allKeys) {headerStr = [headerStr stringByAppendingString:key];headerStr = [headerStr stringByAppendingString:@": "];if ([headerFields objectForKey:key]) {headerStr = [headerStr stringByAppendingString:headerFields[key]];}headerStr = [headerStr stringByAppendingString:@"\n"];}NSData *headerData = [headerStr dataUsingEncoding:NSUTF8StringEncoding];headersLength = headerData.length;}return headersLength;}

Body

对于 Body 的计算,上文看到有些文章里采用的 expectedContentLength 或者去 NSURLResponse 对象的 allHeaderFields 中获取 Content-Length 值,其实都不够准确。

首先 API 文档中对 expectedContentLength 也有介绍是不准确的:

其次,HTTP 1.1 标准里也有介绍 Content-Length 字段不一定是每个 Response 都带有的,最重要的是,Content-Length 只是表示 Body 部分的大小。

我的方式是,在前面代码中有写到,在 didReceiveData 中对 dm_data 进行了赋值

didReceiveData:

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {[self.client URLProtocol:self didLoadData:data];[self.dm_data appendData:data];}

那么在 stopLoading 方法中,就可以拿到本次网络请求接收到的数据。但需要注意对 gzip 情况进行区别分析。我们知道 HTTP 请求中,客户端在发送请求的时候会带上 Accept-Encoding,这个字段的值将会告知服务器客户端能够理解的内容压缩算法。而服务器进行相应时,会在 Response 中添加 Content-Encoding 告知客户端选中的压缩算法。

所以,我们在 stopLoading 中获取 Content-Encoding,如果使用了 gzip,则模拟一次 gzip 压缩,再计算字节大小:

- (void)stopLoading {[self.connection cancel];DMNetworkTrafficLog *model = [[DMNetworkTrafficLog alloc] init];model.path = self.request.URL.path;model.host = self.request.URL.host;model.type = DMNetworkTrafficDataTypeResponse;model.lineLength = [self.dm_response dm_getLineLength];model.headerLength = [self.dm_response dm_getHeadersLength];if ([self.dm_response isKindOfClass:[NSHTTPURLResponse class]]) {NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)self.dm_response;NSData *data = self.dm_data;if ([[httpResponse.allHeaderFields objectForKey:@"Content-Encoding"] isEqualToString:@"gzip"]) {// 模拟压缩data = [self.dm_data gzippedData];}model.bodyLength = data.length;}model.length = model.lineLengthmodel.headerLengthmodel.bodyLength;[model settingOccurTime];[[DMDataManager defaultDB] addNetworkTrafficLog:model];}

这里 gzippedData 参考这个库的内容[9]

[[DMDataManager defaultDB] addNetworkTrafficLog:model]; 是调用持久化层的代码将数据落库。

记录 Resquest 信息

Line

很遗憾,对于NSURLRequest 我没有像 NSURLReponse 一样幸运的找到私有接口将其转换成 CFNetwork 相关数据,但是我们很清楚 HTTP 请求报文 Line 部分的组成,所以我们可以添加一个方法,获取一个经验 Line。

同样为 NSURLReques 添加一个扩展:NSURLRequest DoggerMonitor

- (NSUInteger)dgm_getLineLength {NSString *lineStr = [NSString stringWithFormat:@"%@ %@ %@\n", self.HTTPMethod, self.URL.path, @"HTTP/1.1"];NSData *lineData = [lineStr dataUsingEncoding:NSUTF8StringEncoding];return lineData.length;}

Header

Header 这里有一个非常大的坑。

request.allHTTPHeaderFields 拿到的头部数据是有很多缺失的,这块跟业内朋友交流的时候,发现很多人都没有留意到这个问题。

缺失的部分不仅仅是上面一篇文章中说到的 Cookie。

如果通过 Charles 抓包,可以看到,会缺失包括但不仅限于以下字段:

1) Accept

2) Connection

3) Host

这个问题非常的迷,同时由于无法转换到 CFNetwork 层,所以一直拿不到准确的 Header 数据。

最后,我在 so 上也找到了两个相关问题,供大家参考

NSUrlRequest: where an app can find the default headers for HTTP request?[10]

NSMutableURLRequest, cant access all request headers sent out from within my iPhone program[11]

两个问题的回答基本表明了,如果你是通过 CFNetwork 来发起请求的,才可以拿到完整的 Header 数据。

所以这块只能拿到大部分的 Header,但是基本上缺失的都固定是那几个字段,对我们流量统计的精确度影响不是很大。

那么主要就针对 cookie 部分进行补全:

- (NSUInteger)dgm_getHeadersLengthWithCookie {NSUInteger headersLength = 0;NSDictionary<NSString *, NSString *> *headerFields = self.allHTTPHeaderFields;NSDictionary<NSString *, NSString *> *cookiesHeader = [self dgm_getCookies];// 添加 cookie 信息if (cookiesHeader.count) {NSMutableDictionary *headerFieldsWithCookies = [NSMutableDictionary dictionaryWithDictionary:headerFields];[headerFieldsWithCookies addEntriesFromDictionary:cookiesHeader];headerFields = [headerFieldsWithCookies copy];}NSLog(@"%@", headerFields);NSString *headerStr = @"";for (NSString *key in headerFields.allKeys) {headerStr = [headerStr stringByAppendingString:key];headerStr = [headerStr stringByAppendingString:@": "];if ([headerFields objectForKey:key]) {headerStr = [headerStr stringByAppendingString:headerFields[key]];}headerStr = [headerStr stringByAppendingString:@"\n"];}NSData *headerData = [headerStr dataUsingEncoding:NSUTF8StringEncoding];headersLength = headerData.length;return headersLength;}

- (NSDictionary<NSString *, NSString *> *)dgm_getCookies {NSDictionary<NSString *, NSString *> *cookiesHeader;NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];NSArray<NSHTTPCookie *> *cookies = [cookieStorage cookiesForURL:self.URL];if (cookies.count) {cookiesHeader = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];}return cookiesHeader;}

body

最后是 body 部分,这里也有个坑。通过 NSURLConnection 发出的网络请求 resquest.HTTPBody 拿到的是 nil。

需要转而通过 HTTPBodyStream 读取 stream 来获取 request 的 Body 大小。

- (NSUInteger)dgm_getBodyLength {NSDictionary<NSString *, NSString *> *headerFields = self.allHTTPHeaderFields;NSUInteger bodyLength = [self.HTTPBody length];if ([headerFields objectForKey:@"Content-Encoding"]) {NSData *bodyData;if (self.HTTPBody == nil) {uint8_t d[1024] = {0};NSInputStream *stream = self.HTTPBodyStream;NSMutableData *data = [[NSMutableData alloc] init];[stream open];while ([stream hasBytesAvailable]) {NSInteger len = [stream read:d maxLength:1024];if (len > 0 && stream.streamError == nil) {[data appendBytes:(void *)d length:len];}}bodyData = [data copy];[stream close];} else {bodyData = self.HTTPBody;}bodyLength = [[bodyData gzippedData] length];}return bodyLength;}

落库

最后在 DMURLProtocol 的 - (nullable NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(nullable NSURLResponse *)response; 方法中对 resquest 调用报文各个部分大小方法后落库:

-(NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response {if (response != nil) {self.dm_response = response;[self.client URLProtocol:self wasRedirectedToRequest:request redirectResponse:response];}DMNetworkTrafficLog *model = [[DMNetworkTrafficLog alloc] init];model.path = request.URL.path;model.host = request.URL.host;model.type = DMNetworkTrafficDataTypeRequest;model.lineLength = [connection.currentRequest dgm_getLineLength];model.headerLength = [connection.currentRequest dgm_getHeadersLengthWithCookie];model.bodyLength = [connection.currentRequest dgm_getBodyLength];model.length = model.lineLengthmodel.headerLengthmodel.bodyLength;[model settingOccurTime];[[DMDataManager defaultDB] addNetworkTrafficLog:model];return request;}

针对 NSURLSession 的处理

直接使用 DMURLProtocol 并 registerClass 并不能完整的拦截所有网络请求,因为通过 NSURLSession 的 sharedSession 发出的请求是无法被 NSURLProtocol 代理的。

我们需要让 [NSURLSessionConfiguration defaultSessionConfiguration].protocolClasses 的属性中也设置我们的 DMURLProtocol,这里通过 swizzle,置换 protocalClasses 的 get 方法:

编写一个 DMURLSessionConfiguration

#import @interface DMURLSessionConfiguration : NSObject@property (nonatomic,assign) BOOL isSwizzle;(DMURLSessionConfiguration *)defaultConfiguration;- (void)load;- (void)unload;@end

#import "DMURLSessionConfiguration.h"#import #import "DMURLProtocol.h"#import "DMNetworkTrafficManager.h"@implementation DMURLSessionConfiguration(DMURLSessionConfiguration *)defaultConfiguration {static DMURLSessionConfiguration *staticConfiguration;static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{staticConfiguration=[[DMURLSessionConfiguration alloc] init];});return staticConfiguration;}- (instancetype)init {self = [super init];if (self) {self.isSwizzle = NO;}return self;}- (void)load {self.isSwizzle = YES;Class cls = NSClassFromString(@"__NSCFURLSessionConfiguration") ?: NSClassFromString(@"NSURLSessionConfiguration");[self swizzleSelector:@selector(protocolClasses) fromClass:cls toClass:[self class]];}- (void)unload {self.isSwizzle=NO;Class cls = NSClassFromString(@"__NSCFURLSessionConfiguration") ?: NSClassFromString(@"NSURLSessionConfiguration");[self swizzleSelector:@selector(protocolClasses) fromClass:cls toClass:[self class]];}- (void)swizzleSelector:(SEL)selector fromClass:(Class)original toClass:(Class)stub {Method originalMethod = class_getInstanceMethod(original, selector);Method stubMethod = class_getInstanceMethod(stub, selector);if (!originalMethod || !stubMethod) {[NSException raise:NSInternalInconsistencyException format:@"Couldn't load NEURLSessionConfiguration."];}method_exchangeImplementations(originalMethod, stubMethod);}- (NSArray *)protocolClasses {// DMNetworkTrafficManager 中的 protocolClasses 可以给使用者设置自定义的 protocolClassesreturn [DMNetworkTrafficManager manager].protocolClasses;}@end

这样,我们写好了方法置换,在执行过该类单例的 load 方法后,[NSURLSessionConfiguration defaultSessionConfiguration].protocolClasses 拿到的将会是我们设置好的 protocolClasses。

如此,我们再为 DMURLProtocol 添加 start 和 stop 方法,用于启动网络监控和停止网络监控:

(void)start {DMURLSessionConfiguration *sessionConfiguration = [DMURLSessionConfiguration defaultConfiguration];for (id protocolClass in [DMNetworkTrafficManager manager].protocolClasses) {[NSURLProtocol registerClass:protocolClass];}if (![sessionConfiguration isSwizzle]) {// 设置交换[sessionConfiguration load];}}(void)end {DMURLSessionConfiguration *sessionConfiguration = [DMURLSessionConfiguration defaultConfiguration];[NSURLProtocol unregisterClass:[DMURLProtocol class]];if ([sessionConfiguration isSwizzle]) {// 取消交换[sessionConfiguration unload];}}

到此,基本完成了整个网络流量监控。

再提供一个 Manger 方便使用者调用:

#import @class DMNetworkLog;@interface DMNetworkTrafficManager : NSObject/** 所有 NSURLProtocol 对外设置接口,可以防止其他外来监控 NSURLProtocol */@property (nonatomic, strong) NSArray *protocolClasses;/** 单例 */(DMNetworkTrafficManager *)manager;/** 通过 protocolClasses 启动流量监控模块 */(void)startWithProtocolClasses:(NSArray *)protocolClasses;/** 仅以 DMURLProtocol 启动流量监控模块 */(void)start;/** 停止流量监控 */(void)end;@end

#import "DMNetworkTrafficManager.h"#import "DMURLProtocol.h"@interface DMNetworkTrafficManager ()@end@implementation DMNetworkTrafficManager#pragma mark - Public(DMNetworkTrafficManager *)manager {static DMNetworkTrafficManager *manager;static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{manager=[[DMNetworkTrafficManager alloc] init];});return manager;}(void)startWithProtocolClasses:(NSArray *)protocolClasses {[self manager].protocolClasses = protocolClasses;[DMURLProtocol start];}(void)start {[self manager].protocolClasses = @[[DMURLProtocol class]];[DMURLProtocol start];}(void)end {[DMURLProtocol end];}@end

五、代码

本文中贴出了比较多的代码,为了便于大家整体观看,可以到 这里[12] 来阅读。

由于其中包含了一些数据操作的内容不需要关心,所以我直接省略了,虽然没有 Demo,但我相信大家都是能理解整个监控结构的。

六、Other

如果你的 APP 从 iOS 9 支持,可以使用 NetworkExtension,通过 NetworkExtension 可以通过 VPN 的形式接管整个网络请求,省掉了上面所有的烦恼。

参考

[1]http://github.com/aozhimin/iOS-Monitor-Platform [2]http://github.com/zixun/GodEye [3]http://github.com/zixun/NetworkEye [4]http://tech.meituan.com/2016/12/19/hertz.html [5]http://liujinlongxa.com/2016/12/20/使用NSURLProtocol注意的一些问题/ [6]http://github.com/Draveness/analyze/blob/master/contents/OHHTTPStubs/iOS 开发中使用 NSURLProtocol 拦截 HTTP 请求.md [7]http://www.jianshu.com/p/49e7282e888d [8]http://developer.apple.com/documentation/foundation/nsurlprotocol?language=occ [9]http://github.com/nicklockwood/GZIP [10]http://stackoverflow.com/questions/5695914/nsurlrequest-where-an-app-can-find-the-default-headers-for-http-request [11]http://stackoverflow.com/questions/21694886/nsmutableurlrequest-cant-access-all-request-headers-sent-out-from-within-my-iph [12]http://github.com/summertian4/iOS-ObjectiveC/tree/master/NetworkTraffic

    推荐阅读
  • 合肥徽宴楼罍街店怎么样(合肥狮城徽宴楼)

    据此,合肥高新区法院一审判决,合肥佳源徽宴楼餐饮有限公司立即停止侵害安徽狮城徽宴楼饮食管理有限公司相关商标专用权的行为,并赔偿狮城徽宴楼经济损失3万元。佳源徽宴楼不服一审判决,提起上诉。近日,合肥中院二审驳回上诉,维持原判。

  • 打着火怠速下多少转最好(打着火怠速下100转最好)

    我们一起去了解并探讨一下这个问题吧!打着火怠速下多少转最好1000转最好。车辆在冷车时启动。发动机为了尽快进入状态,会在高转速下运行,俗称热车~如果说你车一启动就在1000转以下,这个你要等待下。转速还再下降,说明你车辆的怠速过低,需要提高一点~如果始终保持不变的转速,说明你车在启动之前,状态很好,不需要热车。

  • 植物大战僵尸搞笑动画植物是反派(青春守沪)

    青春守沪青年报·青春上海记者范彦萍“有一天,小草正在散步,突然病毒老大冲了过来小草很害怕,撒腿就跑,眼看病毒老大就要追上来了,忽然,西瓜和辣椒跑了过来,与病毒老大发起了战斗……”最近,上海理工大学附属小学教育。

  • 波比认猪猪侠老大(超人特工队强势回归)

    《超人特工队2》的焦点在妈妈海伦身上,她骑着重型机车阻止各种悲剧,屡创功劳。续集《超人特工队2》时隔14年强势回归。《超2》延续首集的内容,由于超人身份引起疑虑,有心人士希望让超人重新在社会获得重视,但事情并非想象的简单,正义感与超能力毫无用武之地,可以想象巴鲍勃的无力感。除了妈妈与小儿子,片中的酷冰侠、小飞、小倩、巴鲍勃,以及其他英雄例如“砖头”与“空间女侠”等,都各有特色,贡献了很多笑点。

  • 猜成语一个太阳和一个星星(众多星星围绕月亮)

    大家好,看图猜成语,你最棒!昨天答案分别是:背道而驰,一鸣惊人,空中楼阁,如虎添翼,拔刀相助成语释义:背道而驰:朝着相反的方向跑。比喻行动方向和所要达到的目标完全相反。比喻增添力量,使强大的更加强大,或使凶恶的更加凶恶。每天进步一点点,离成功更进一步,加油!

  • 湖南端午节有哪些风俗拨龙舟(端午佳节看龙舟飞渡)

    沅江和溆水泛舟是江口的魅力所在。溆浦的传统龙舟一般头高尾翘,成梭子形,全长大约在26至28米之间,尾部翘起呈燕尾状,尾高达2.8米,可载70余人。两艘龙舟共108人,意取梁山泊108将。凡成年男子不管身处何地均必须赶回参加,或组织或自发,供四面八方慕名而来的客人亲友观赏。铜湾镇今年将有30多艘龙舟下水,场面非常热闹。全长约28米,28节,可坐人24节,计48人。黄溪口龙舟比赛没有号令,没有起跑线,观众是裁判。

  • 红米note5a手机一直重复重启该怎么办(这5种办法都可供选择)

    红米note5a手机一直重复重启该怎么办?红米note5a手机一直重复重启该怎么办首先遇到这个情况,先查看一下手机的是否还有电量,如果没有足够的电量的话,红米Note就会出现这个问题,解决这个问题的方法很简单,那就是为手机充电。另外,红米Note无限自动重启的也有可能是因为安装了root之后,把系统的自带的软件卸载掉,从而导致红米Note无限重启。

  • 饭后多久运动(饭后多久运动合适及原因)

    吃完饭多长时间可以运动,主要看采取运动强度是怎么样的如果是轻度运动,比如太极拳或者散步,如果不是太饱的情况之下,饭后10分钟左右就可以进行但是如果是一些高强度的体育锻炼,需要2个小时后甚至更长时间,比如长跑、打篮球、拳击、健身等建议饭后2小时以上,甚至更久再活动,我来为大家科普一下关于饭后多久运动?如果是轻度运动,比如太极拳或者散步,如果不是太饱的情况之下,饭后10分钟左右就可以进行。

  • 鳜鱼养殖周期,育苗培育、鱼种培育及成鱼养殖3个阶段

    鳜鱼养殖周期主要分为鱼苗培育阶段、鱼种培育阶段及成鱼养殖阶段。在放进鳜鱼苗之前,鱼池都要进行消毒,然后还可以放一些培育水质用来培育饵料鱼。