iOS UIWebView小整理(三)(利用NSURLProtocol加載本地js、css資源)

今天要整理的是:NSURLProtocol

NSURLProtocol是 URL Loading System的一部分翘地,它是一個(gè)抽象類(lèi),想要使用子類(lèi)繼承并實(shí)現(xiàn)癌幕、然后注冊(cè)衙耕。

URLLoadingSystem.png

它能干嘛?

攔截基于系統(tǒng)的NSUIConnection或者NSUISession進(jìn)行封裝的網(wǎng)絡(luò)請(qǐng)求(如使用WKWebView勺远、UIWebView)

單單一個(gè)攔截橙喘,這里就可以實(shí)現(xiàn)很多你想要實(shí)現(xiàn)的功能,例如:

  • 解決dns域名劫持(白名單或黑名單)
  • 加載本地緩存胶逢、本地js渴杆、css、image等靜態(tài)資源
  • 修改request宪塔,重定向

實(shí)現(xiàn)

首先實(shí)現(xiàn)是繼承的NSURLProtocol的子類(lèi)

#import <Foundation/Foundation.h>
@interface NSURLProtocolCustom : NSURLProtocol
@end

然后在合適的地方進(jìn)行注冊(cè)

[NSURLProtocol registerClass:[CustomURLProtocol class]];

一經(jīng)注冊(cè)之后磁奖,所有交給URL Loading system的網(wǎng)絡(luò)請(qǐng)求都會(huì)被攔截,所以當(dāng)不需要攔截的時(shí)候某筐,要進(jìn)行注銷(xiāo)

[NSURLProtocol unregisterClass:[NSURLProtocolCustom class]];

在CustomURLProtocol里必須要實(shí)現(xiàn)的方法:

canInitWithRequest.png

canInitWithRequest 簡(jiǎn)單的說(shuō)是請(qǐng)求的入口比搭,所有的請(qǐng)求都會(huì)先進(jìn)入到這里,如果希望攔截下來(lái)自己處理南誊,那么就返回YES身诺,否則就返回NO。
需要注意的地方是抄囚,當(dāng)自己去處理這個(gè)請(qǐng)求的時(shí)候霉赡,由于URL Loading System會(huì)創(chuàng)建一個(gè)CustomURLProtocol實(shí)例后通過(guò)NSURLConnection去獲取數(shù)據(jù),NSURLConnection又會(huì)調(diào)用URL Loading System幔托,這樣會(huì)造成死循環(huán)穴亏,解決的方法是通過(guò)setProperty:forKey:inRequest:標(biāo)記那些已經(jīng)處理過(guò)的request蜂挪,如果是已經(jīng)處理的就返回NO。


canonicalRequestForRequest.png

通常canonicalRequestForRequest方法你可以直接返回request嗓化,但也可以在這里做修改棠涮,比如添加header,修改host等

loading.png

當(dāng)需要自己處理該請(qǐng)求的時(shí)候刺覆,startLoading便會(huì)被調(diào)起严肪,在這里你可以處理自己的邏輯。

加載本地js谦屑、css資源驳糯,緩存h5圖片

在實(shí)際項(xiàng)目運(yùn)行中,很多時(shí)候我們需要加載h5氢橙,但由于h5里有很多基礎(chǔ)的js與css结窘,這些基礎(chǔ)資源基本不會(huì)有變化或者有些資源過(guò)大,網(wǎng)絡(luò)不穩(wěn)定的情況下會(huì)出現(xiàn)加載中斷充蓝,所以在做webview優(yōu)化的時(shí)候,我們可以抽離出這部分的資源文件喉磁,打包到項(xiàng)目里來(lái)谓苟,然后使用NSURLProtocol來(lái)進(jìn)行加載,以減少這部分文件的網(wǎng)絡(luò)請(qǐng)求协怒。同樣的涝焙,h5里的圖片資源也可以通過(guò)NSURLProtocol加上SDWebImage進(jìn)行加載并且緩存。

代碼:

#import "NSURLProtocolCustom.h"
#import "NSString+PJR.h"
#import "UIImageView+WebCache.h"
#import "NSData+ImageContentType.h"
#import "UIImage+MultiFormat.h"

static NSString* const FilteredKey = @"FilteredKey";

@interface NSURLProtocolCustom ()

@property (nonatomic, strong) NSMutableData *responseData;
@property (nonatomic, strong) NSURLConnection *connection;
@end

@implementation NSURLProtocolCustom

+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
    NSURL *url = request.URL;
    NSString *scheme = [url scheme];
    
    if ( ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame ||
          [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame))
    {
        NSString *requestUrl = url.absoluteString;
        
        NSLog(@"HTTPMethod:%@",request.HTTPMethod);
        NSLog(@"Protocol URL:%@",requestUrl);
        
        NSString *extension = url.pathExtension;
        
        if ([requestUrl containsString:@"://resource"]) {
            //基礎(chǔ)靜態(tài)資源孕暇,h5上已經(jīng)做了統(tǒng)一路徑處理仑撞,可以以此:://resource 前序來(lái)判斷是否需要攔截
            if ([extension isEqualToString:@"js"] || [extension isEqualToString:@"css"]) {
                NSString *fileName = [[request.URL.absoluteString componentsSeparatedByString:@"/"].lastObject componentsSeparatedByString:@"?"][0];
                NSLog(@"fileName is %@",fileName);
                //從bundle中讀取
                NSString *path = [[NSBundle mainBundle] pathForResource:fileName ofType:nil];
                //從沙箱讀取
      //        NSString *docPath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"];
      //        NSString *path = [docPath stringByAppendingPathComponent:fileName];
                if (path) {
                    NSLog(@"the file is local file");
                    return [NSURLProtocol propertyForKey:FilteredKey inRequest:request] == nil;
                }
            }
        }
        
        if ([extension isPicResource]) {
            //用sdwebimage來(lái)加載圖片,間接實(shí)現(xiàn)h5圖片緩存功能
            return [NSURLProtocol propertyForKey:FilteredKey inRequest:request]== nil;
        }
        
    }
    
    return NO;
}

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
    return request;
}

- (void)startLoading
{
    NSURL *url = super.request.URL;
    NSString *requestUrl = url.absoluteString;
    NSString *extension = url.pathExtension;
    
    NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
    //標(biāo)記該請(qǐng)求已經(jīng)處理
    [NSURLProtocol setProperty:@YES forKey:FilteredKey inRequest:mutableReqeust];
    
    if ([requestUrl containsString:@"://resource"]) {
        //獲取本地資源路徑
        NSString *fileName = [[requestUrl componentsSeparatedByString:@"/"].lastObject componentsSeparatedByString:@"?"][0];
        //從bundle中讀取
        NSString *path = [[NSBundle mainBundle] pathForResource:fileName ofType:nil];
        //從沙箱讀取
//        NSString *docPath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"];
//        NSString *path = [docPath stringByAppendingPathComponent:fileName];
        if (path) {
            //根據(jù)路徑獲取MIMEType
            CFStringRef pathExtension = (__bridge_retained CFStringRef)[path pathExtension];
            CFStringRef type = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension, NULL);
            CFRelease(pathExtension);
            
            //The UTI can be converted to a mime type:
            NSString *mimeType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass(type, kUTTagClassMIMEType);
            if (type != NULL)
                CFRelease(type);
            
            //加載本地資源
            NSData *data = [NSData dataWithContentsOfFile:path];
            [self sendResponseWithData:data mimeType:mimeType];
            
            return;
        }else{
            NSLog(@"%@ is not find",fileName);
        }
    }else if([extension isPicResource]){
        //處理圖片緩存
        NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:url];
        UIImage *img = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:key];
        
        if (img) {
            //存在圖片緩存
            NSData *data = nil;
            if ([extension isEqualToString:@"png"]) {
                data = UIImagePNGRepresentation(img);
            }else{
                data = UIImageJPEGRepresentation(img, 1);
            }
            NSString *mimeType = [NSData sd_contentTypeForImageData:data];
            [self sendResponseWithData:data mimeType:mimeType];
            
            return ;
        }
        
        self.connection = [NSURLConnection connectionWithRequest:mutableReqeust delegate:self];
    }
}

- (void)stopLoading
{
    NSLog(@"stopLoading from networld");
    if (self.connection) {
        [self.connection cancel];
    }
}

- (void)sendResponseWithData:(NSData *)data mimeType:(nullable NSString *)mimeType
{
    NSLog(@"sendResponseWithData start");
    // 這里需要用到MIMEType
    NSURLResponse *response = [[NSURLResponse alloc] initWithURL:super.request.URL
                                                        MIMEType:mimeType
                                           expectedContentLength:data.length
                                                textEncodingName:nil];
    
    if ([self client]) {
        if ([self.client respondsToSelector:@selector(URLProtocol:didReceiveResponse:cacheStoragePolicy:)]) {
            [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
        }
        if ([self.client respondsToSelector:@selector(URLProtocol:didLoadData:)]) {
            [[self client] URLProtocol:self didLoadData:data];
        }
        if ([self.client respondsToSelector:@selector(URLProtocolDidFinishLoading:)]) {
            [[self client] URLProtocolDidFinishLoading:self];
        }
    }
    
    NSLog(@"sendResponseWithData end");
}

#pragma mark- NSURLConnectionDelegate

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    
    [self.client URLProtocol:self didFailWithError:error];
}

#pragma mark - NSURLConnectionDataDelegate
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    self.responseData = [[NSMutableData alloc] init];
    [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}

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

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    //緩存圖片
    UIImage *cacheImage = [UIImage sd_imageWithData:self.responseData];
    NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
    
    [[SDImageCache sharedImageCache] storeImage:cacheImage
                           recalculateFromImage:NO
                                      imageData:self.responseData
                                         forKey:key
                                         toDisk:YES];
    
    [self.client URLProtocolDidFinishLoading:self];
}

@end

最后

  • 對(duì)于WKWebView妖滔,想要使用NSURLProtocol的話(huà)隧哮,注冊(cè)的時(shí)候記得加上以下代碼
Class cls = NSClassFromString(@"WKBrowsingContextController");
        SEL sel = NSSelectorFromString(@"registerSchemeForCustomProtocol:");
        if ([(id)cls respondsToSelector:sel]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
            [(id)cls performSelector:sel withObject:@"http"];
            [(id)cls performSelector:sel withObject:@"https"];
#pragma clang diagnostic pop
        }
  • 靜態(tài)資源如果想要更新,可以單獨(dú)開(kāi)接口去下載座舍,保存到沙箱中沮翔,同時(shí)將讀取文件的方式由bundle改為沙箱。

demo

以上便是NSURLProtocol一些介紹曲秉,上面提到的加載本地資源文件demo MYLCustom也已經(jīng)放到github采蚀,有興趣的可以下載研究兼打個(gè)call。

個(gè)人站點(diǎn)記錄

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末承二,一起剝皮案震驚了整個(gè)濱河市榆鼠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌亥鸠,老刑警劉巖妆够,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡责静,警方通過(guò)查閱死者的電腦和手機(jī)袁滥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)灾螃,“玉大人题翻,你說(shuō)我怎么就攤上這事⊙恚” “怎么了嵌赠?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)熄赡。 經(jīng)常有香客問(wèn)我姜挺,道長(zhǎng),這世上最難降的妖魔是什么彼硫? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任炊豪,我火速辦了婚禮,結(jié)果婚禮上拧篮,老公的妹妹穿的比我還像新娘词渤。我一直安慰自己,他們只是感情好串绩,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布缺虐。 她就那樣靜靜地躺著,像睡著了一般礁凡。 火紅的嫁衣襯著肌膚如雪高氮。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,764評(píng)論 1 290
  • 那天顷牌,我揣著相機(jī)與錄音剪芍,去河邊找鬼。 笑死窟蓝,一個(gè)胖子當(dāng)著我的面吹牛紊浩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播疗锐,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼坊谁,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了滑臊?” 一聲冷哼從身側(cè)響起口芍,我...
    開(kāi)封第一講書(shū)人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎雇卷,沒(méi)想到半個(gè)月后鬓椭,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體颠猴,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年小染,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了翘瓮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡裤翩,死狀恐怖资盅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情踊赠,我是刑警寧澤呵扛,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站筐带,受9級(jí)特大地震影響今穿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜伦籍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一蓝晒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧帖鸦,春花似錦芝薇、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)慢逾。三九已至立倍,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間侣滩,已是汗流浹背口注。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留君珠,地道東北人寝志。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像策添,于是被迫代替她去往敵國(guó)和親材部。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理唯竹,服務(wù)發(fā)現(xiàn)乐导,斷路器,智...
    卡卡羅2017閱讀 134,629評(píng)論 18 139
  • 概覽 緩存組件應(yīng)該說(shuō)是每個(gè)客戶(hù)端程序必備的核心組件浸颓,試想對(duì)于每個(gè)界面的訪問(wèn)都必須重新請(qǐng)求勢(shì)必降低用戶(hù)體驗(yàn)物臂。但是如何...
    默默_David閱讀 1,918評(píng)論 1 9
  • 前幾天在簡(jiǎn)書(shū)上發(fā)布了一篇《準(zhǔn)大學(xué)生們,這些話(huà)難道還沒(méi)人告訴你仪媒?》沉桌,不想這篇即興之作成為熱門(mén)文章。隨后规丽,我收到很多簡(jiǎn)...
    霖曦_閱讀 26,224評(píng)論 338 1,267
  • 你是不是經(jīng)常告訴自己赌莺,今晚我一定要早點(diǎn)睡冰抢,結(jié)果到了第二天又后悔,哎艘狭,昨晚睡的好晚挎扰,然后陷入深深的自責(zé)里。 你有沒(méi)有...
    員子圓夢(mèng)閱讀 3,444評(píng)論 10 27