公司新項(xiàng)目里面,主要是以原生作為框架,詳情頁(yè)面有web頁(yè)面負(fù)責(zé)展示。iOS這邊是用WKWebView來(lái)加載web頁(yè)面,但是由于網(wǎng)絡(luò)環(huán)境和頁(yè)面復(fù)雜度的問(wèn)題,iOS 端和安卓 的web加載速度很慢。(客戶(hù)服務(wù)器域名的問(wèn)題)
1、把所有web的資源包放到本地:?jiǎn)栴}依舊在
2、web頁(yè)面共有的資源包放到原生本地,web頁(yè)面加載時(shí),先判斷本地是否有資源包,優(yōu)先加載本地資源包。
參考:https://segmentfault.com/a/1190000005732602
https://blog.csdn.net/u011154007/article/details/68068172
https://blog.csdn.net/hanhailong18/article/details/79394856
核心原理:
對(duì)H5請(qǐng)求進(jìn)行攔截,如果本地已經(jīng)有對(duì)應(yīng)的靜態(tài)資源文件,則直接加載,這樣就能達(dá)到“秒開(kāi)”webview的效果。
對(duì)于iOS而言,這就需要用到NSURLProtocol這個(gè)神器了。接下來(lái),分析下它到底是什么東西,我們?cè)趺蠢盟_(dá)到上述效果。
NSURLProtocol:它能夠讓你去重新定義蘋(píng)果的URL加載系統(tǒng)(URL Loading System)的行為,URL Loading System里有許多類(lèi)用于處理URL請(qǐng)求,比如NSURL,NSURLRequest,NSURLConnection和NSURLSession等。當(dāng)URL Loading System使用NSURLRequest去獲取資源的時(shí)候,它會(huì)創(chuàng)建一個(gè)NSURLProtocol子類(lèi)的實(shí)例,你不應(yīng)該直接實(shí)例化一個(gè)NSURLProtocol,NSURLProtocol看起來(lái)像是一個(gè)協(xié)議,但其實(shí)這是一個(gè) 類(lèi),而且必須使用該類(lèi)的子類(lèi),并且需要被注冊(cè)。
換句話說(shuō),NSURLProtocol能攔截所有當(dāng)前app下的網(wǎng)絡(luò)請(qǐng)求,并且能自定義地進(jìn)行處理。
直接上代碼
#import "NSURLProtocolCustom.h"
#import <CoreFoundation/CoreFoundation.h>
#import <MobileCoreServices/MobileCoreServices.h>
static NSString* const FilteredKey = @"FilteredKey";
@interface NSURLProtocolCustom()<NSURLConnectionDataDelegate>
@property(nonatomic, strong)NSURLConnection *coonection;
@end
@implementation NSURLProtocolCustom
//這個(gè)方法的作用是判斷當(dāng)前protocol是否要對(duì)這個(gè)request進(jìn)行處理(所有的網(wǎng)絡(luò)請(qǐng)求都會(huì)走到這里,所以我們只需要對(duì)我們產(chǎn)生的request進(jìn)行處理即可)。
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
NSString *extension = request.URL.pathExtension;
BOOL isSource = [@[@"png", @"jpeg", @"gif", @"jpg", @"js", @"css"] indexOfObjectPassingTest:^BOOL(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
return [extension compare:obj options:NSCaseInsensitiveSearch] == NSOrderedSame;
}] != NSNotFound;
return [NSURLProtocol propertyForKey:FilteredKey inRequest:request] == nil && isSource;
}
//可以對(duì)request進(jìn)行預(yù)處理,比如對(duì)header加一些東西什么的,我們這里沒(méi)什么要改的,所以直接返回request就好了。
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
return request;
}
//我們這里需要做一件事,就是自己拼裝httpResponse,并且返回給url load system,然后到了webview那一層,會(huì)收到response,對(duì)于webview而言,加載本地和走網(wǎng)絡(luò)拿到的response是完全一樣的。所以上述代碼展示了如何拼裝一個(gè)httpResponse,當(dāng)組裝完成后,需要調(diào)用self.client將數(shù)據(jù)傳出去。
- (void)startLoading
{
//fileName 獲取web頁(yè)面加載的資源包文件名(js css等)
NSString *fileName = [super.request.URL.absoluteString componentsSeparatedByString:@"/"].lastObject;
//這里是獲取本地資源路徑 如:png,js等
NSString *path = [[NSBundle mainBundle] pathForResource:fileName ofType:nil];
NSLog(@"fileName is %@ path=%@",fileName,path);
if (!path) {
//本地資源包沒(méi)有所需的文件,加載網(wǎng)絡(luò)請(qǐng)求
NSMutableURLRequest *newrequest = [self.request mutableCopy];
newrequest.allHTTPHeaderFields = self.request.allHTTPHeaderFields;
[NSURLProtocol setProperty:@YES forKey:FilteredKey inRequest:newrequest];
self.coonection = [NSURLConnection connectionWithRequest:newrequest delegate:self];
return;
}
//根據(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];
}
- (void)stopLoading
{
[self.coonection cancel];
NSLog(@"stopLoading, something went wrong!");
}
- (void)sendResponseWithData:(NSData *)data mimeType:(nullable NSString *)mimeType
{
NSMutableDictionary *header = [[NSMutableDictionary alloc]initWithCapacity:2];
NSString *contentType = [mimeType stringByAppendingString:@";charset=UTF-8"];
header[@"Content-Type"] = contentType;
header[@"Content-Length"] = [NSString stringWithFormat:@"%lu",(unsigned long) data.length];
NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc]initWithURL:self.request.URL statusCode:200 HTTPVersion:@"1.1" headerFields:header];
// 這里需要用到MIMEType
//NSURLResponse *response = [[NSURLResponse alloc] initWithURL:super.request.URL MIMEType:mimeType expectedContentLength:-1 textEncodingName:nil];
//硬編碼 開(kāi)始嵌入本地資源到web中
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
[[self client] URLProtocol:self didLoadData:data];
[[self client] URLProtocolDidFinishLoading:self];
}
#pragma 代理
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
}
-(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
目前,關(guān)于攔截并加載本地資源包的代碼操作就沒(méi)有了,剩下還有一步,就是在加載web頁(yè)面之前,對(duì)自定義的NSURLProtocol 進(jìn)行注冊(cè),使上面的代碼實(shí)現(xiàn)對(duì)資源的攔截
-(void)setNSURLProtocolCustom{
//注冊(cè)
[NSURLProtocol registerClass:[NSURLProtocolCustom class]];
//實(shí)現(xiàn)攔截功能,這個(gè)是核心
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
}
}
結(jié)語(yǔ):之前單純加載web頁(yè)面的時(shí)候,加載時(shí)間基本都是在兩秒以上,現(xiàn)在基本控制在兩秒以?xún)?nèi)了 。