思路: 使用NSURLProtocol攔截請求轉發到本地。
本文作者為 簡書-melo的微博 / 掘金-melo的微博 / Github-meloalright,轉載請註明出處哦。
1.確認離線化需求
部門負責的app有一部分使用的線上h5頁,長期以來載入略慢...
於是考慮使用離線化載入。
確保[低速網路]或[無網路]可網頁秒開。
複製程式碼
2.使用[NSURLProtocol]攔截
區別於uiwebview wkwebview使用如下方法攔截
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 區別於uiwebview wkwebview使用如下方法攔截
Class cls = NSClassFromString(@"WKBrowsingContextController");
SEL sel = NSSelectorFromString(@"registerSchemeForCustomProtocol:");
if ([(id)cls respondsToSelector:sel]) {
[(id)cls performSelector:sel withObject:@"http"];
[(id)cls performSelector:sel withObject:@"https"];
}
}
複製程式碼
# 註冊NSURLProtocol攔截
- (IBAction)regist:(id)sender {
[NSURLProtocol registerClass:[FilteredProtocol class]];
}
複製程式碼
# 登出NSURLProtocol攔截
- (IBAction)unregist:(id)sender {
[NSURLProtocol unregisterClass:[FilteredProtocol class]];
}
複製程式碼
3.下載[zip] + 使用[SSZipArchive]解壓
需要先 #import "SSZipArchive.h
- (void)downloadZip {
NSDictionary *_headers;
NSURLSession *_session = [self sessionWithHeaders:_headers];
NSURL *url = [NSURL URLWithString: @"http://10.2.138.225:3238/dist.zip"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 初始化cachepath
NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSFileManager *fm = [NSFileManager defaultManager];
// 刪除之前已有的檔案
[fm removeItemAtPath:[cachePath stringByAppendingPathComponent:@"dist.zip"] error:nil];
NSURLSessionDownloadTask *downloadTask=[_session downloadTaskWithRequest:request completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
if (!error) {
NSError *saveError;
NSURL *saveUrl = [NSURL fileURLWithPath: [cachePath stringByAppendingPathComponent:@"dist.zip"]];
// location是下載後的臨時儲存路徑,需要將它移動到需要儲存的位置
[[NSFileManager defaultManager] copyItemAtURL:location toURL:saveUrl error:&saveError];
if (!saveError) {
NSLog(@"task ok");
if([SSZipArchive unzipFileAtPath:
[cachePath stringByAppendingPathComponent:@"dist.zip"]
toDestination:cachePath]) {
NSLog(@"unzip ok");// 解壓成功
}
else {
NSLog(@"unzip err");// 解壓失敗
}
}
else {
NSLog(@"task err");
}
}
else {
NSLog(@"error is :%@", error.localizedDescription);
}
}];
[downloadTask resume];
}
複製程式碼
4.遷移資源至[NSTemporary]
[wkwebview]真機不支援直接載入[NSCache]資源
需要先遷移資源至[NSTemporary]
- (void)migrateDistToTempory {
NSFileManager *fm = [NSFileManager defaultManager];
NSString *cacheFilePath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"dist"];
NSString *tmpFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"dist"];
// 先刪除tempory已有的dist資源
[fm removeItemAtPath:tmpFilePath error:nil];
NSError *saveError;
// 從caches拷貝dist到tempory臨時資料夾
[[NSFileManager defaultManager] copyItemAtURL:[NSURL fileURLWithPath:cacheFilePath] toURL:[NSURL fileURLWithPath:tmpFilePath] error:&saveError];
NSLog(@"Migrate dist to tempory ok");
}
複製程式碼
5.轉發請求
如果[/static]開頭 => 則轉發[Request]到本地[.css/.js]資源
如果[index.html]結尾 => 就直接[Load]本地[index.html] (否則[index.html]可能會載入失敗)
//
// ProtocolCustom.m
// proxy-browser
//
// Created by melo的微博 on 2018/4/8.
// Copyright © 2018年 com. All rights reserved.
//
#import <objc/runtime.h>
#import <Foundation/Foundation.h>
#import <MobileCoreServices/MobileCoreServices.h>
static NSString*const matchingPrefix = @"http://10.2.138.225:3233/static/";
static NSString*const regPrefix = @"http://10.2.138.225:3233";
static NSString*const FilteredKey = @"FilteredKey";
@interface FilteredProtocol : NSURLProtocol
@property (nonatomic, strong) NSMutableData *responseData;
@property (nonatomic, strong) NSURLConnection *connection;
@end
複製程式碼
@implementation FilteredProtocol
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
return [NSURLProtocol propertyForKey:FilteredKey inRequest:request]== nil;
}
複製程式碼
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
NSLog(@"Got it request.URL.absoluteString = %@",request.URL.absoluteString);
NSMutableURLRequest *mutableReqeust = [request mutableCopy];
//擷取重定向
if ([request.URL.absoluteString hasPrefix:matchingPrefix])
{
NSURL* proxyURL = [NSURL URLWithString:[FilteredProtocol generateProxyPath: request.URL.absoluteString]];
NSLog(@"Proxy to = %@", proxyURL);
mutableReqeust = [NSMutableURLRequest requestWithURL: proxyURL];
}
return mutableReqeust;
}
複製程式碼
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b
{
return [super requestIsCacheEquivalent:a toRequest:b];
}
複製程式碼
# 如果[index.html]結尾 => 就直接[Load]本地[index.html]
- (void)startLoading {
NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
// 標示改request已經處理過了,防止無限迴圈
[NSURLProtocol setProperty:@YES forKey:FilteredKey inRequest:mutableReqeust];
if ([self.request.URL.absoluteString hasSuffix:@"index.html"]) {
NSURL *url = self.request.URL;
NSString *path = [FilteredProtocol generateDateReadPath: self.request.URL.absoluteString];
NSLog(@"Read data from path = %@", path);
NSFileHandle *file = [NSFileHandle fileHandleForReadingAtPath:path];
NSData *data = [file readDataToEndOfFile];
NSLog(@"Got data = %@", data);
[file closeFile];
//3.拼接響應Response
NSInteger dataLength = data.length;
NSString *mimeType = [self getMIMETypeWithCAPIAtFilePath:path];
NSString *httpVersion = @"HTTP/1.1";
NSHTTPURLResponse *response = nil;
if (dataLength > 0) {
response = [self jointResponseWithData:data dataLength:dataLength mimeType:mimeType requestUrl:url statusCode:200 httpVersion:httpVersion];
} else {
response = [self jointResponseWithData:[@"404" dataUsingEncoding:NSUTF8StringEncoding] dataLength:3 mimeType:mimeType requestUrl:url statusCode:404 httpVersion:httpVersion];
}
//4.響應
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
[[self client] URLProtocol:self didLoadData:data];
[[self client] URLProtocolDidFinishLoading:self];
}
else {
self.connection = [NSURLConnection connectionWithRequest:mutableReqeust delegate:self];
}
}
複製程式碼
- (void)stopLoading
{
if (self.connection != nil)
{
[self.connection cancel];
self.connection = nil;
}
}
複製程式碼
- (NSString *)getMIMETypeWithCAPIAtFilePath:(NSString *)path
{
if (![[[NSFileManager alloc] init] fileExistsAtPath:path]) {
return nil;
}
CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[path pathExtension], NULL);
CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass (UTI, kUTTagClassMIMEType);
CFRelease(UTI);
if (!MIMEType) {
return @"application/octet-stream";
}
return (__bridge NSString *)(MIMEType);
}
複製程式碼
#pragma mark - 拼接響應Response
- (NSHTTPURLResponse *)jointResponseWithData:(NSData *)data dataLength:(NSInteger)dataLength mimeType:(NSString *)mimeType requestUrl:(NSURL *)requestUrl statusCode:(NSInteger)statusCode httpVersion:(NSString *)httpVersion
{
NSDictionary *dict = @{@"Content-type":mimeType,
@"Content-length":[NSString stringWithFormat:@"%ld",dataLength]};
NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:requestUrl statusCode:statusCode HTTPVersion:httpVersion headerFields:dict];
return response;
}
複製程式碼
#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 {
[self.client URLProtocolDidFinishLoading:self];
}
複製程式碼
+ (NSString *)generateProxyPath:(NSString *) absoluteURL {
NSString *tmpFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"dist"];
NSString *fileAbsoluteURL = [@"file:/" stringByAppendingString:tmpFilePath];
return [absoluteURL stringByReplacingOccurrencesOfString:regPrefix
withString:fileAbsoluteURL];
}
複製程式碼
+ (NSString *)generateDateReadPath:(NSString *) absoluteURL {
NSString *fileDataReadURL = [NSTemporaryDirectory() stringByAppendingPathComponent:@"dist"];
return [absoluteURL stringByReplacingOccurrencesOfString:regPrefix
withString:fileDataReadURL];
}
@end
複製程式碼
結語:
完整[DEMO]請參考: github.com/meloalright…
(∩_∩)求給個☆哦
鳴謝:
參考文件:
1.簡書: iOS UIWebView小整理(三)(利用NSURLProtocol載入本地js、css資源)
2.Github: Yeatse/NSURLProtoc…
3.簡書: 獲得檔案的MIME Type