思路: 使用NSURLProtocol拦截请求转发到本地。
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