ios wkwebview离线化加载h5资源解决方案

思路: 使用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

结语:

完整[DEMO]请参考: https://github.com/meloalright/wk-proxy

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

您可能感兴趣的文章:

  • WKWebView、WebView和JS的交互方式详解
  • iOS WKWebView无法处理URL Scheme和App Store链接的问题解决
  • JS交互点击WKWebView中的图片实现预览效果
  • iOS11 WKWebView问题汇总
  • iOS11 WKWebView 无法加载内容的解决方法
  • iOS中WKWebView白屏问题的分析与解决
  • iOS11 WKWebView内容过滤规则详解
  • IOS中UIWebView、WKWebView之JS交互
  • 清除WKWebView cookies的方法
  • ios下OC与JS交互之WKWebView
(0)

相关推荐

  • iOS11 WKWebView 无法加载内容的解决方法

    问题描述: iOS9和iOS10用WKWebView加载URL都没有问题,iOS11却是一片空白 可能是用了 NSMutableURLRequest ,iOS11貌似不支持 NSMutableURLRequest ,无论是用 UIWebView 还是 WKWebView ,都不支持 NSMutableURLRequest 解决方法参考 if #available(iOS 11, *) { let request = NSURLRequest.init(url: URL.init(string:

  • WKWebView、WebView和JS的交互方式详解

    由于Xcode8发布之后,编译器开始不支持iOS 7了,这样我们的app也改为最低支持iOS 8.0,既然需要与web交互,那自然也就选择使用了 iOS 8.0之后 才推出的新控件 WKWebView. 相比与 UIWebView, WKWebView 存在很多优势: 支持更多的HTML5的特性 高达60fps滚动刷新频率与内置手势 与Safari相容的JavaScript引擎 在性能.稳定性方面有很大提升占用内存更少 协议方法及功能都更细致 可获取加载进度等. UIWebView与JS的交互方

  • IOS中UIWebView、WKWebView之JS交互

    做客户端开发,肯定避免不了JS交互,于是自己对苹果接口做了个简易封装: JSExport-->UIWebView+Interaction.WKScriptMessageHandler -->WKWebView+Interaction以备以后使用. 代码非常简洁,见这里:https://github.com/V5zhou/JSInteraction.git 旧方式 旧的交互方式有通过UIWebViewDelegate实现的:JS与客户端定义好跳转页面参数,在将要跳转时捕获关键字,然后处理业务.

  • ios下OC与JS交互之WKWebView

    上一篇文章我们使用了JavaScriptCore框架重写了之前的示例,iOS8苹果偏爱HTML5,重构了UIWebVIew,给我们带来了WKWebView,使其性能.稳定性.功能大幅度提升,也更好的支持了HTML5的新特性.这篇文章就们就拿WKWebView来小试牛刀 一.WKWebView Framework WKWebView的14个类与3个协议: WKBackForwardList: 之前访问过的 web 页面的列表,可以通过后退和前进动作来访问到. WKBackForwardListIt

  • iOS11 WKWebView问题汇总

    问题一描述: iOS9和iOS10用WKWebView加载URL都没有问题,iOS11却是一片空白 可能是用了NSMutableURLRequest,iOS11貌似不支持NSMutableURLRequest,无论是用UIWebView还是WKWebView,都不支持NSMutableURLRequest 解决方法参考 if #available(iOS 11, *) { let request = NSURLRequest.init(url: URL.init(string: urlStr)!

  • iOS中WKWebView白屏问题的分析与解决

    前言 随着WKWebView的推出, 解决了很多UIWebView 的问题.比如加载速度慢,内存泄露等问题.WKWebView是在iOS 8 推出,前段时间正好把项目也适配到iOS 8 以上了,终于可以把项目中的UIWebView 替换成WKWebView. WKWebView的特点: 性能高,稳定性好,占用的内存比较小, 支持JS交互 支持HTML5 新特性 可以添加进度条(然并卵,不好用,还是习惯第三方的). 支持内建手势, 据说高达60fps的刷新频率(不卡) 但是发现在使用的时候还是有很

  • iOS WKWebView无法处理URL Scheme和App Store链接的问题解决

    WKWebView简介 UIWebView自iOS2就有,WKWebView从iOS8才有,毫无疑问WKWebView将逐步取代笨重的UIWebView.通过简单的测试即可发现UIWebView占用过多内存,且内存峰值更是夸张.WKWebView网页加载速度也有提升,但是并不像内存那样提升那么多. 下面列举一些其它的优势: 1.更多的支持HTML5的特性 2.官方宣称的高达60fps的滚动刷新率以及内置手势 3.Safari相同的JavaScript引擎,且允许JavaScript的Nitro库

  • JS交互点击WKWebView中的图片实现预览效果

    Swift 4.0 WKWebView 1.注入js代码 (重点) func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { let jsGetImages = "function getImages(){" + "var objs = document.getElementsByTagName(\"img\");" + "var imgScr =

  • iOS11 WKWebView内容过滤规则详解

    WKWebView中新增了一个功能,可以对WebView的内容添加一些自定义的过滤规则.这个功能原来在 Safari Extension 中被引入,从 11 开始同样适用于WKWebView. 使用方法 原理上就是提供一个 JSON 给 WebKit,这个 JSON 包括内容的触发规则(trigger)和对应的处理方式(action).比如: [{ "trigger": { "url-filter": ".*" }, "action&q

  • 清除WKWebView cookies的方法

    在UIWebView下,可以使用 [[NSURLCache sharedURLCache] removeAllCachedResponses];//清除缓存 WKWebView清除cookies的方法(iOS9以上) WKWebsiteDataStore *dateStore = [WKWebsiteDataStore defaultDataStore]; [dateStore fetchDataRecordsOfTypes:[WKWebsiteDataStore allWebsiteDataT

随机推荐