iOS Network Monitoring: NSURLProtocol, Hook Techniques, and NSURLSessionTaskMetrics
The article explains the challenges of mobile network environments in China, introduces connection migration issues, and presents iOS network monitoring solutions using NSURLProtocol, code injection (Hook) with Method Swizzling, NSProxy, and Fishhook, as well as detailed usage of NSURLSessionTaskMetrics for performance analysis.
China's mobile network environment is complex, with Wi‑Fi, 4G, 3G, 2.5G (Edge) and 2G coexisting, causing frequent connection migrations and DNS problems that can lead to app failures and user loss. Monitoring network performance from the app perspective helps pinpoint root causes.
Network monitoring on iOS is typically implemented via NSURLProtocol or code injection (Hook). While NSURLProtocol is convenient, it only supports FTP, HTTP and HTTPS, limiting its ability to monitor other protocols; using lower‑level CFNetwork removes this restriction.
Key performance indicators include TCP connection time, DNS time, SSL time, first‑byte time, response time, HTTP error rate and network error rate.
NSURLProtocol implementation
static NSString * const HJHTTPHandledIdentifier = @"hujiang_http_handled";
@interface HJURLProtocol ()
@property (nonatomic, strong) NSURLSessionDataTask *dataTask;
@property (nonatomic, strong) NSOperationQueue *sessionDelegateQueue;
@property (nonatomic, strong) NSURLResponse *response;
@property (nonatomic, strong) NSMutableData *data;
@property (nonatomic, strong) NSDate *startDate;
@property (nonatomic, strong) HJHTTPModel *httpModel;
@end
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
if (![request.URL.scheme isEqualToString:@"http"] &&
![request.URL.scheme isEqualToString:@"https"]) {
return NO;
}
if ([NSURLProtocol propertyForKey:HJHTTPHandledIdentifier inRequest:request]) {
return NO;
}
return YES;
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
NSMutableURLRequest *mutableReqeust = [request mutableCopy];
[NSURLProtocol setProperty:@YES forKey:HJHTTPHandledIdentifier inRequest:mutableReqeust];
return [mutableReqeust copy];
}
- (void)startLoading {
self.startDate = [NSDate date];
self.data = [NSMutableData data];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
self.sessionDelegateQueue = [[NSOperationQueue alloc] init];
self.sessionDelegateQueue.maxConcurrentOperationCount = 1;
self.sessionDelegateQueue.name = @"com.hujiang.wedjat.session.queue";
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:self.sessionDelegateQueue];
self.dataTask = [session dataTaskWithRequest:self.request];
[self.dataTask resume];
// ... additional bookkeeping ...
}
- (void)stopLoading {
[self.dataTask cancel];
self.dataTask = nil;
// ... collect response data ...
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics {
// Use metrics to obtain DNS, TCP, TLS, request/response timings
}Hook techniques (AOP)
Method Swizzling – replace Objective‑C method implementations at runtime.
NSProxy – create a proxy object that forwards messages to the original delegate while inserting custom logic.
Fishhook – a Facebook‑open‑source library that rewrites C function symbols (e.g., in CFNetwork) using dyld.
Examples of Hook implementations:
- (instancetype)initWithStream:(id)stream {
if (self = [super init]) {
_stream = stream;
}
return self;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [_stream methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
[anInvocation invokeWithTarget:_stream];
} - (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len {
NSInteger readSize = [_stream read:buffer maxLength:len];
// record readSize
return readSize;
} static CFReadStreamRef (*original_CFReadStreamCreateForHTTPRequest)(CFAllocatorRef, CFHTTPMessageRef);
static CFReadStreamRef XX_CFReadStreamCreateForHTTPRequest(CFAllocatorRef alloc, CFHTTPMessageRef request) {
CFReadStreamRef originalCFStream = original_CFReadStreamCreateForHTTPRequest(alloc, request);
NSInputStream *stream = (__bridge NSInputStream *)originalCFStream;
XXInputStreamProxy *outStream = [[XXInputStreamProxy alloc] initWithClient:stream];
CFRelease(originalCFStream);
return (__bridge_retained CFReadStreamRef)outStream;
}
void save_original_symbols() {
original_CFReadStreamCreateForHTTPRequest = dlsym(RTLD_DEFAULT, "CFReadStreamCreateForHTTPRequest");
}
rebind_symbols((struct rebinding[1]){{"CFReadStreamCreateForHTTPRequest", XX_CFReadStreamCreateForHTTPRequest, (void *)&original_CFReadStreamCreateForHTTPRequest}}, 1);NSURLSessionTaskMetrics introduced in iOS 10 provides detailed timing data such as domainLookupStartDate , connectStartDate , secureConnectionStartDate , requestStartDate , responseStartDate , and their corresponding end dates, as well as properties like taskInterval , redirectCount , resourceFetchType , and flags for proxy or reused connections.
By combining NSURLProtocol or Hook methods with NSURLSessionTaskMetrics , developers can achieve comprehensive real‑user monitoring (RUM) of network performance on iOS, helping to diagnose DNS latency, TCP handshakes, TLS negotiations, and server response times.
The article concludes that iOS network monitoring can be realized either through NSURLProtocol or Hook techniques, each with code examples, and that the newer metrics APIs enable fine‑grained performance analysis.
Hujiang Technology
We focus on the real-world challenges developers face, delivering authentic, practical content and a direct platform for technical networking among developers.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.