我有一个自定义NSURLProtocol(“UrlProtocol”),用于在导航到特定网站时拦截来自UIWebView的请求,并在发送之前应用额外的HTTP标头.我跟着
https://www.raywenderlich.com/59982/nsurlprotocol-tutorial上班了.我的问题是从已弃用的NSURLConnection切换到NSURLSession:我测试了一个非常简单的单文件html页面,它已成功加载.但是,使用js文件,图像等资源的稍微复杂的站点将会超时,而使用NSURLConnection时,整个站点将在几秒钟内加载.
我将使用NSURLConnection粘贴原始UrlProtocol,然后使用NSURLSession粘贴新类.原本的:
#import "UrlProtocol.h" #import "Globals.h" @implementation UrlProtocol + (BOOL)canInitWithRequest:(NSURLRequest *)request { if (![request.URL.absoluteString hasPrefix:@"http"]) return NO; //No need to intercept non-http requests if ([NSURLProtocol propertyForKey:@"handled" inRequest:request]) { return NO; } return YES; } + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { NSString* key = @"custom-auth-header"; Globals* globals = [Globals getInstance]; NSString* token = [globals token]; NSMutableURLRequest *newRequest = [request mutableCopy]; //Create a mutable copy that can be modified [newRequest setValue:token forHTTPHeaderField:key]; return [newRequest copy]; //return a non-mutable copy } + (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b { return [super requestIsCacheEquivalent:a toRequest:b]; } - (void)startLoading { NSMutableURLRequest *newRequest = [self.request mutableCopy]; [NSURLProtocol setProperty:@YES forKey:@"handled" inRequest:newRequest]; self.connection = [NSURLConnection connectionWithRequest:newRequest delegate:self]; } - (void)stopLoading { NSLog(@"stopLoading"); [self.connection cancel]; self.connection = nil; } - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { [self.client URLProtocol:self didLoadData:data]; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { [self.client URLProtocolDidFinishLoading:self]; } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { [self.client URLProtocol:self didFailWithError:error]; } @end
针对每个请求使用NSURLSessionDataTasks的新UrlProtocol:
#import "UrlProtocol.h" #import "Globals.h" @implementation UrlProtocol + (BOOL)canInitWithRequest:(NSURLRequest * _Nonnull) request { if (![request.URL.absoluteString hasPrefix:@"http"]) return NO; //No need to intercept non-http requests if ([NSURLProtocol propertyForKey:@"handled" inRequest:request]) { return NO; } return YES; } + (NSURLRequest * _Nonnull)canonicalRequestForRequest:(NSURLRequest * _Nonnull)request { NSString* key = @"custom-auth-header"; Globals* globals = [Globals getInstance]; NSString* token = [globals token]; NSMutableURLRequest *newRequest = [request mutableCopy]; //Create a mutable copy that can be modified [newRequest setValue:token forHTTPHeaderField:key]; return [newRequest copy]; //return a non-mutable copy } + (BOOL)requestIsCacheEquivalent:(NSURLRequest * _Nonnull)a toRequest:(NSURLRequest * _Nonnull)b { return [super requestIsCacheEquivalent:a toRequest:b]; } - (void)startLoading { NSMutableURLRequest *newRequest = [self.request mutableCopy]; [NSURLProtocol setProperty:@YES forKey:@"handled" inRequest:newRequest]; [Globals setUrlSessionDelegate:self]; Globals* globals = [Globals getInstance]; self.dataTask = [globals.session dataTaskWithRequest:newRequest]; [self.dataTask resume]; } - (void)URLSession:(NSURLSession * _Nonnull)session dataTask:(NSURLSessionDataTask * _Nullable)dataTask didReceiveData:(NSData * _Nullable)data{ [self.client URLProtocol:self didLoadData:data]; } - (void)URLSession:(NSURLSession * _Nonnull)session dataTask:(NSURLSessionDataTask * _Nullable)dataTask didReceiveResponse:(NSURLResponse * _Nullable)response completionHandler:(void (^ _Nullable)(NSURLSessionResponseDisposition))completionHandler{ [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; completionHandler(NSURLSessionResponseAllow); } - (void)URLSession:(NSURLSession * _Nonnull)session task:(NSURLSessionTask * _Nonnull)task didCompleteWithError:(NSError * _Nullable)error{ if (error){ [self.client URLProtocol:self didFailWithError:error]; } else { [self.client URLProtocolDidFinishLoading:self]; } } - (void)URLSession:(NSURLSession * _Nonnull)session task:(NSURLSessionTask * _Nonnull)task willPerformHTTPRedirection:(NSHTTPURLResponse * _Nonnull)response newRequest:(NSURLRequest * _Nonnull)request completionHandler:(void (^ _Nonnull)(NSURLRequest * _Nullable))completionHandler { completionHandler(request); } - (void)stopLoading { [self.dataTask cancel]; self.dataTask = nil; } @end
“Globals”是一个单例,我已经初始化了一个NSURLSession,意在整个应用程序的运行时使用.它还包含我为所有请求设置为自定义HTTP标头的令牌:
#import "Globals.h" #import "UrlProtocol.h" @implementation Globals @synthesize token; @synthesize session; static Globals *instance = nil; +(Globals*) getInstance { @synchronized(self) { if (instance == nil) { instance = [Globals new]; } } return instance; } //UrlProtocol class has no init method,so the NSURLSession delegate is being set on url load. We will ensure only one NSURLSession is created. +(void) setUrlSessionDelegate:(UrlProtocol*) urlProtocol{ Globals* globals = [Globals getInstance]; if (!globals.session){ globals.session = [NSURLSession sessionWithConfiguration:NSURLSessionConfiguration.defaultSessionConfiguration delegate:urlProtocol delegateQueue:nil]; } } @end
解决方法
通过为每个NSURLSessionDataTask创建新的默认NSURLSession解决了我的问题.我试图为我的所有任务分享一个NSURLSession的方式出了点问题. URLProtocol的startLoading方法现在如下:
- (void)startLoading { NSMutableURLRequest *newRequest = [self.request mutableCopy]; [NSURLProtocol setProperty:@YES forKey:@"handled" inRequest:newRequest]; NSURLSessionConfiguration* config = NSURLSessionConfiguration.defaultSessionConfiguration; NSURLSession* session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil]; self.dataTask = [session dataTaskWithRequest:newRequest]; [self.dataTask resume]; }