preface

The widely used open source libraries of iOS network libraries are AFNetworking and Mars of wechat, and most of them realize their own network libraries based on AFNetworking. It is quite simple to implement network monitoring. However, if you are in a basic service group and need non-intrusive monitoring on the service side, you will encounter some difficulties. The problems encountered in APM special network index monitoring are recorded here.

App network request process

NSURLSessionTaskDelegate New delegate method

Since iOS 10, a new delegate method has been added to NSURLSessionTaskDelegate:

/* * Sent when complete statistics information has been collected for the task. */ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE (macosx (10.12), the ios (10.0), watchos (3.0), tvos (10.0));Copy the code

Don’t give up daddy Apple’s sugar just because iOS 9 isn’t available. Never mind the number of lost users.

There are three schemes as follows. Scheme 3 is to achieve non-intrusive business. If there are not too many projects in the company and there is no so-called basic service library, scheme 1 and Scheme 2 are directly used.

Scheme 1: NSURLProtocol monitors App network requests

You can customize Custom Protocol for network monitoring.

+ (BOOL)canInitWithRequest:(NSURLRequest *)request { } - (void)startLoading { } #pragma NSURLSessionTaskDelegate - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task DidFinishCollectingMetrics: (NSURLSessionTaskMetrics *) metrics index reported} {/ / implementation networkCopy the code

disadvantages

ProtocolClasses need to be implemented. The problem is that multiple custom URlProtocols respond to only one. This scheme can be implemented if no other custom URLProtocol is available on the business side of the project. If you are in the basic service group and the projects in the department have customized URLProtocol, the customized URLProtocol logical code cannot be implemented on the service side.

NSURLSessionConfiguration *config=[NSURLSessionConfiguration defaultSessionConfiguration];
config.protocolClasses=@[[CustomURLProtocol class]];
Copy the code

Scheme 2: Implement MetricsBlock of AFNetworking

- (void)setTaskDidFinishCollectingMetricsBlock:(nullable void (^)(NSURLSession *session, NSURLSessionTask *task, NSURLSessionTaskMetrics * _Nullable Metrics))block AF_API_AVAILABLE(ios(10), MacOSx (10.12), Watchos (3), TVOs (10));Copy the code

This block can be implemented in the basic service network library of the division to report network indicators.

disadvantages

  1. This block is available only in AFNetworking 4.0.0 and must be upgraded to this version.
  2. This block is also implemented on the business side of individual projects, resulting in coverage problems.
  3. Some projects did not use the network library of the division, and encapsulated AFNetworking independently to realize network requests. So you can’t monitor it;

Scheme three: Hook AFNetworking to achieve NSURLSessionTaskDelegate agent method

No matter AFNetworking is 3.2.1 or below or 4.0.0 or above, and no matter whether the project team uses the basic service network library in the department, only requests are initiated using AFNetworking library. Will be able to achieve network indicators monitoring.

advantages

It can be implemented in other basic service libraries, such as the basic service APM library in the division, instead of the basic service network library, to realize network monitoring and report index data through APM.

Code implementation

AFHTTPSessionManager+APM.h


#import "AFHTTPSessionManager.h"

NS_ASSUME_NONNULL_BEGIN

@interface AFHTTPSessionManager (APM)

@end

NS_ASSUME_NONNULL_END
Copy the code

AFHTTPSessionManager+APM.m

#import "AFHTTPSessionManager+APM.h" #import "APMNetworkMetricsModel.h" #import <objc/runtime.h> static inline void swizzling_exchangeMethod(Class clazz ,SEL originalSelector, SEL swizzledSelector){ Method originalMethod = class_getInstanceMethod(clazz, originalSelector); Method swizzledMethod = class_getInstanceMethod(clazz, swizzledSelector); BOOL success = class_addMethod(clazz, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (success) { class_replaceMethod(clazz, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } } @implementation AFHTTPSessionManager (APM) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ swizzling_exchangeMethod(self, @selector(URLSession:task:didFinishCollectingMetrics:), @selector(apm_URLSession:task:didFinishCollectingMetrics:)); }); } - (void)apm_URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics { [self apm_URLSession:session task:task didFinishCollectingMetrics:metrics]; NSLog(@"metrics: %@", metrics); If (@ the available (iOS 10.0, *) && metrics.transactionMetrics.count) { [metrics.transactionMetrics enumerateObjectsUsingBlock:^(NSURLSessionTaskTransactionMetrics * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if (obj == nil) return;  APMNetworkMetricsModel *model = [APMNetworkMetricsModel networkMetricsModelWithMetrics:obj]; // The sampling rate can be set dynamically through the service side. If there is no need to report all the samples. // The sampling rate will be executed here after the report through the APM. }} + (BOOL) resolveInstanceMethod SEL (SEL) {/ / AFNetworking 4.0.0 achieve didFinishCollectingMetrics / / but 4.0.0 does not implement the following, Method swap crashes, So in this dynamic addition Method if (sel = = @ the selector (URLSession: task: didFinishCollectingMetrics:)) {Method Method = class_getInstanceMethod(self, @selector(doMethod)); IMP imp = method_getImplementation(method); const char *typeEncoding = method_getTypeEncoding(method); return class_addMethod(self, sel, imp, typeEncoding); } return [super resolveInstanceMethod:sel]; } - (void)doMethod {// lonely empty implementation} @endCopy the code

APMNetworkMetricsModel.h

Attribute definitions are underlined to facilitate the mapping table and are ignored.

#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface APMNetworkMetricsModel : NSObject @property (nonatomic, copy) NSString *path; // the requested URL @property (nonatomic, copy) NSString * req_URL; @property (nonatomic, copy) NSString *req_params; @property (nonatomic, strong) NSDictionary *req_headers; @property (nonatomic, assign) int64_t req_header_byte; @property (nonatomic, assign) int64_t req_body_byte; @property (nonatomic, strong) NSDictionary *res_headers; /// response code @property (nonatomic, assign) NSInteger status_code; @property (nonatomic, assign) int64_t res_header_byte; @property (nonatomic, assign) int64_t res_body_byte; /// HTTP method @property (nonatomic, copy) NSString *http_method; @property (nonatomic, copy) NSString *protocol_name; @property (nonatomic, assign) BOOL proxy_connection; /// whether cellular connection @property (nonatomic, assign) BOOL cellular; @property (nonatomic, copy) NSString * local_IP; @property (nonatomic, assign) NSInteger local_port; /// remote IP @property (nonatomic, copy) NSString * remote_IP; @property (nonatomic, assign) NSInteger remote_port; #pragma mark-cost time @property (nonatomic, assign) int64_t dns_time; @property (nonatomic, assign) int64_t tcp_time; @property (nonatomic, assign) int64_t ssl_time; @property (nonatomic, assign) int64_t req_time; @property (nonatomic, assign) int64_t res_time; /// Response Response time @property (nonatomic, assign) int64_t res_time; @property (nonatomic, assign) int64_t req_total_time; @end @class NSURLSessionTaskTransactionMetrics; @interface APMNetworkMetricsModel (Helper) + (instancetype)networkMetricsModelWithMetrics:(NSURLSessionTaskTransactionMetrics *)metrics; @end NS_ASSUME_NONNULL_ENDCopy the code

APMNetworkMetricsModel.m

#import "APMNetworkMetricsModel.h" @implementation APMNetworkMetricsModel @end @implementation APMNetworkMetricsModel (Helper) + (instancetype)networkMetricsModelWithMetrics:(NSURLSessionTaskTransactionMetrics *)metrics { if (metrics.resourceFetchType == NSURLSessionTaskMetricsResourceFetchTypeNetworkLoad) { APMNetworkMetricsModel *model = [[APMNetworkMetricsModel alloc] init]; / / need in basic service network libraries on the header Settings interface model. The path = metrics. Request. AllHTTPHeaderFields (@ "path"); model.req_url = [metrics.request.URL absoluteString]; model.req_params = [metrics.request.URL parameterString]; model.req_headers = metrics.request.allHTTPHeaderFields; If (@ the available (iOS 13.0, *)) {model. Req_header_byte = metrics. CountOfRequestHeaderBytesSent; model.req_body_byte = metrics.countOfRequestBodyBytesSent; model.res_header_byte = metrics.countOfResponseHeaderBytesReceived; model.res_body_byte = metrics.countOfResponseBodyBytesReceived; } if (@available(iOS 13.0, *)) {model.local_ip = metrics. LocalAddress; model.local_port = metrics.localPort.integerValue; model.remote_ip = metrics.remoteAddress; model.remote_port = metrics.remotePort.integerValue; } if (@available(iOS 13.0, *)) {model. Cellular = metrics. Cellular; } NSHTTPURLResponse *response = (NSHTTPURLResponse *)metrics.response; if ([response isKindOfClass:NSHTTPURLResponse.class]) { model.res_headers = response.allHeaderFields; model.status_code = response.statusCode; } model.http_method = metrics.request.HTTPMethod; model.protocol_name = metrics.networkProtocolName; model.proxy_connection = metrics.proxyConnection; if (metrics.domainLookupStartDate && metrics.domainLookupEndDate) { model.dns_time = ceil([metrics.domainLookupEndDate timeIntervalSinceDate:metrics.domainLookupStartDate] * 1000); } if (metrics.connectStartDate && metrics.connectEndDate) { model.tcp_time = ceil([metrics.connectEndDate timeIntervalSinceDate:metrics.connectStartDate] * 1000); } if (metrics.secureConnectionStartDate && metrics.secureConnectionEndDate) { model.ssl_time = ceil([metrics.secureConnectionEndDate timeIntervalSinceDate:metrics.secureConnectionStartDate] * 1000); } if (metrics.requestStartDate && metrics.requestEndDate) { model.req_time = ceil([metrics.requestEndDate timeIntervalSinceDate:metrics.requestStartDate] * 1000); } if (metrics.responseStartDate && metrics.responseEndDate) { model.res_time = ceil([metrics.responseEndDate timeIntervalSinceDate:metrics.responseStartDate] * 1000); } if (metrics.fetchStartDate && metrics.responseEndDate) { model.req_total_time = ceil([metrics.responseEndDate timeIntervalSinceDate:metrics.fetchStartDate] * 1000); } return model; } return nil; } @endCopy the code