NSURLProtocol
官方解釋一個抽象類,用于處理特定于協(xié)議的URL數(shù)據(jù)的加載。
使用方法
不要NSURLProtocol直接實例化子類。而是為您的應(yīng)用支持的任何自定義協(xié)議或URL方案創(chuàng)建子類。下載開始時,系統(tǒng)會創(chuàng)建相應(yīng)的協(xié)議對象來處理相應(yīng)的URL請求。您可以在應(yīng)用程序的啟動時間內(nèi)定義協(xié)議類并調(diào)用類方法,以便系統(tǒng)了解您的協(xié)議。registerClass:
用NSURLProtocol可以統(tǒng)一處理app內(nèi)你發(fā)的協(xié)議,例如你要對請求頭進行處理加工,對請求以及響應(yīng)處理都是個不錯的地方
1.首先創(chuàng)建一個繼承于NSURLProtocol的類
#import <Foundation/Foundation.h>
@interface XiaDianProtocol : NSURLProtocol
@end
2.然后先在app開啟的時候加入如下代碼,注冊自定義協(xié)議類,這樣你發(fā)的請求都會通過你這類進行過濾在進行下一步操作
[NSURLProtocol registerClass:[XiaDianProtocol class]];

3.寫一個簡單網(wǎng)絡(luò)請求,API在網(wǎng)上找的看一下效果
//創(chuàng)建一個網(wǎng)絡(luò)路徑
NSString *browseUrl = [NSString stringWithFormat:@"https://www.sojson.com/open/api/weather/json.shtml?city=%@", @"北京"];
//處理一下特殊字符漢字等
browseUrl = [browseUrl stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]];
//創(chuàng)建一個網(wǎng)絡(luò)請求
NSURLRequest *request =[NSURLRequest requestWithURL:[NSURL URLWithString:browseUrl]];
//創(chuàng)建一個Task任務(wù):
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *sessionDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (data == nil) {
return ;
}
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:(NSJSONReadingMutableLeaves) error:nil];
NSLog(@"從服務(wù)器獲取到數(shù)據(jù)%@", dict);
}];
NSLog(@"sessionDataTask------>%p", sessionDataTask);
//執(zhí)行任務(wù)
[sessionDataTask resume];
4.運行結(jié)果當然是崩的 因為protocol里有必須要實現(xiàn)的方法 要不崩至少要實現(xiàn)有下面幾個API
| 方法API | 注釋 |
|---|---|
| + (BOOL)canInitWithRequest:(NSURLRequest *)request; | 確定協(xié)議子類是否可以處理指定的請求。 |
| - (void)startLoading; | 啟動特定于協(xié)議的請求加載。 |
| - (void)stopLoading; | 停止特定于協(xié)議的請求加載。 |
| + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request; | 返回指定請求的規(guī)范版本。 |
5.把這四個都實現(xiàn)一下,不要返回值的都可以先空著
// return YES 就是都進行處理抓到就處理
+ (BOOL)canInitWithRequest:(NSURLRequest *)request{
return YES;
}
//返回規(guī)范版本的請求一般直接返回,改變影響查找URL緩存中的對象
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request{
return request;
}
// 啟動特定于協(xié)議的請求加載。
- (void)startLoading{
}
// 停止特定于協(xié)議的請求加載。
-(void)stopLoading{
}
6.運行發(fā)送請求正常順序就是canInitWithRequest-》canonicalRequestForRequest-》startLoading-》stopLoading
但你會發(fā)現(xiàn)你的網(wǎng)絡(luò)請求都會是超時的,接收不到數(shù)據(jù)了
因為你攔截了你發(fā)的網(wǎng)絡(luò)請求然后什么也沒有做,這個請求就相當于沒有發(fā)......所以我們要完善一下startloading方法里的東西 這里我們要做的事就是把攔截的請求發(fā)出去然后返回到外面的請求回調(diào)里
- (void)startLoading{
//復(fù)制一份獲取攔截的請求
NSMutableURLRequest *request = [self.request copy];
NSURLSessionDataTask *sessionDataTask = [[NSURLSession sharedSession] dataTaskWithRequest:self.request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
//將獲取的數(shù)據(jù)回傳給外面的請求
[self.client URLProtocol:self didLoadData:data];
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
[self.client URLProtocol:self didFailWithError:error];
[self.client URLProtocolDidFinishLoading:self];
}];
[sessionDataTask resume];
}
7.運行你會發(fā)現(xiàn)一個更大的問題 死循環(huán)了 因為你用的是NSURLSessionDataTask發(fā)的請求 還會被攔截到 攔截到再發(fā) 再攔,所以我們要對我們在startLoading里的請求做一下標識不讓它被攔截 原理就是我們在request對象里人為的添加鍵值進行標識是否被處理了 如果被處理了就在canInitWithRequest方法里返回No不攔截
//定義一個字符串做key
static NSString *xiaDianDealDone = @"xiaDianDealDone";
//修改后的startLoading方法
- (void)startLoading{
NSMutableURLRequest *request = [self.request copy];
//為request對象添加一個鍵值標記為YES
[NSURLProtocol setProperty:@(YES) forKey:xiaDianDealDone inRequest:request];
NSURLSessionDataTask *sessionDataTask = [[NSURLSession sharedSession] dataTaskWithRequest:self.request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
//將獲取的數(shù)據(jù)回傳給外面的請求
[self.client URLProtocol:self didLoadData:data];
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
[self.client URLProtocol:self didFailWithError:error];
[self.client URLProtocolDidFinishLoading:self];
}];
[sessionDataTask resume];
}
//處理后的canInitWithRequest方法
+ (BOOL)canInitWithRequest:(NSURLRequest *)request{
//發(fā)現(xiàn)是處理過的請求直接返回No不攔截此請求
if ([NSURLProtocol propertyForKey:xiaDianDealDone inRequest:request]) {
return NO;
}
return YES;
}
8.這時候運行就可以在外部獲得網(wǎng)絡(luò)數(shù)據(jù)了 在外面的請求完全看不出來做了什么處理。如果你要對網(wǎng)絡(luò)請求統(tǒng)一做某些處理的時候就在這個protol中就好了
理解
每個網(wǎng)絡(luò)請求被攔截的時候系統(tǒng)都會生成一個protocol子類的對象
這個對象有倆個重要屬性用來處理這個請求
| 屬性 | 類型 | 注釋 |
|---|---|---|
| request | NSURLRequest | 攔截的請求的request對象有這個對象能獲取很多request信息 |
| client | id <NSURLProtocolClient> | 這個是回調(diào)回去重要的屬性,每發(fā)一個網(wǎng)絡(luò)請求系統(tǒng)應(yīng)該都會產(chǎn)生一個client對象來處理網(wǎng)絡(luò)請求進行回調(diào)等操作,而所有的client對象都應(yīng)該遵守的<NSURLProtocolClient>協(xié)議 這樣我們通過回調(diào)協(xié)議的方法就可以把數(shù)據(jù)以及響應(yīng)返回最初的請求 |
總結(jié)
1.startLoading 里面隨便你用什么再次發(fā)送攔截的網(wǎng)絡(luò)請求 只要能請求就行
2.startLoading里對應(yīng)的回調(diào)方法要回調(diào)對應(yīng)的<NSURLProtocolClient>協(xié)議方法,這樣就能對應(yīng)的在外面獲取到對應(yīng)的響應(yīng)
3.注意死循環(huán)發(fā)送,加上標識。
4.給予蘋果NSURLSession或NSURLConnection的http,https請求可以攔截 如果公司自己實現(xiàn)的應(yīng)用層協(xié)議就不好使了。
5.還有一點多次注冊protocol子類會按照后注冊的線調(diào)用來運行 如果處理的請求就不在像后找,沒處理就接著向后尋找處理protocol
NSURLProtocol很強大 在canInitWithRequest和startLoading里能做的事情就有很多,網(wǎng)絡(luò)請求處理的黑魔法。