计划1: Scheme 阻拦

1. 计划是什么?

Scheme阻拦是一种网络恳求阻拦战略,主要经过界说自界说的URL Scheme和相应的处理办法,使得能够在网络恳求时阻拦并处理特定的URL恳求。在iOS中,咱们能够运用WKURLSchemeHandler来完结这个功能。苹果为WKWebView供给了一个自界说scheme的功能,即WKURLSchemeHandler,能够阻拦自界说scheme的恳求,然后回来咱们自界说的数据。比方咱们能够阻拦一切”myScheme://”的恳求,然后回来咱们的离线资源。

2. 这个计划是为了处理什么问题而规划的?

URL Scheme原本是被规划出来用于在Web和Native运用之间进行交互的。在iOS平台上,咱们能够为一个运用界说自己的URL Scheme,然后其他运用或许Web页面就能够经过这个URL Scheme来启动该运用并传递参数。关于WKWebView来说,iOS 11开端引入了WKURLSchemeHandler这个接口,让咱们能够阻拦WKWebView中的自界说URL Scheme恳求,从而让咱们有时机自界说这些恳求的处理办法,比方从本地加载一个资源文件,或许做一些特别的处理。

3. 这种计划怎样展开作业,处理办法是什么?

详细的完结步骤如下:

界说自界说的URL Scheme和相应的处理办法:在iOS中,能够经过完结WKURLSchemeHandler协议来界说怎样处理自界说的URL Scheme。

将自界说的URL Scheme与WebView关联:运用WKWebViewConfiguration的setURLSchemeHandler:forURLScheme:办法,将自界说的URL Scheme和对应的处理办法关联起来。

在网络恳求时阻拦特定的URL:当WebView遇到自界说的URL Scheme时,会调用相应的WKURLSchemeHandler进行处理。

处理阻拦的URL:在WKURLSchemeHandler的办法中,咱们能够决定怎样处理阻拦的URL。例如,能够从本地缓存中获取相应的资源,并将其回来给WebView。

4. 这个计划存在什么问题, 怎样处理这些问题?

存在的问题包含:

WKWebView的scheme阻拦只能阻拦页面内部链接的资源,关于浏览器主动加载的资源(如CSS背景图画)或JavaScript动态加载的资源,无法进行阻拦。处理计划是尝试修正前端代码,使得一切资源都经过HTML链接加载,或许运用Service Workers或其他阻拦技能进行阻拦。

虽然咱们能够阻拦并供给缓存的资源,可是何时将这些资源烘托给浏览器还是一个问题。处理计划是,在页面的onload事件后进行资源的烘托。

事例:

首先,咱们需求在HTML中运用自界说的scheme,例如:

<img src="myScheme://image.png" />

然后,在iOS端,咱们需求完结一个遵从WKURLSchemeHandler协议的类:

class MySchemeHandler: NSObject, WKURLSchemeHandler {
    func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
        // 依据urlSchemeTask.request来判别要回来什么资源
        if let url = urlSchemeTask.request.url,
           let path = url.path,
           let resourcePath = Bundle.main.path(forResource: path, ofType: nil),
           let data = try? Data(contentsOf: URL(fileURLWithPath: resourcePath)) {
            let response = URLResponse(url: url, mimeType: "image/png", expectedContentLength: data.count, textEncodingName: nil)
            urlSchemeTask.didReceive(response)
            urlSchemeTask.didReceive(data)
            urlSchemeTask.didFinish()
        } else {
            urlSchemeTask.didFailWithError(NSError(domain: "Domain", code: 0, userInfo: nil))
        }
    }
    func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
        // 停止加载
    }
}

然后在创建WKWebView的时分,注册这个scheme handler:

let configuration = WKWebViewConfiguration()
let schemeHandler = MySchemeHandler()
configuration.setURLSchemeHandler(schemeHandler, forURLScheme: "myScheme")
let webView = WKWebView(frame: .zero, configuration: configuration)

这样,当WKWebView在加载一个运用”myScheme://”的资源时,就会调用咱们的MySchemeHandler来获取数据。

计划2: 前端打包本地静态文件

1. 计划是什么?

这个计划中,咱们将一切前端资源(HTML,CSS,JavaScript,图片等)打包到一个本地文件或许目录中(比方一个ZIP文件),然后直接在WebView中加载这个本地文件或许目录。

2. 这个计划是为了处理什么问题而规划的?

在网络不稳定或许没有网络的环境下,经过本地加载网页资源,咱们能够保证用户能够正常运用咱们的Web运用,而不受网络环境的影响。

3. 这种计划怎样展开作业,处理办法是什么?

前端打包本地静态文件的一般步骤如下:

运用东西打包前端资源:能够运用如Webpack这样的东西将一切前端资源打包到一个ZIP文件中。

将ZIP文件放到运用的本地目录:能够在运用装置时将ZIP文件放到运用的沙盒目录中,或许在运用运行时下载ZIP文件并解压到沙盒目录中。

在WebView中加载本地资源:运用file协议在WebView中加载沙盒目录中的资源。

下面是运用Webpack打包前端资源的一个根本装备示例:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: './src/index.html'
    }),
  ],
  mode: 'production'
};
4. 这个计划存在什么问题, 怎样处理这些问题?

跨域问题:由于浏览器的安全战略,本地文件或许无法正常的拜访网络资源。处理办法是运用CORS进行跨域资源共享,或许经过设置WebView的安全战略来允许跨域拜访。

更新问题:当前端资源需求更新时,需求从头打包并下载新的ZIP文件。处理办法是在运用启动时查看资源的更新,并下载新的资源。

缓存问题:关于一些大的或许不常更新的资源,或许不需求每次都从头下载。处理办法是运用缓存战略,如HTTP缓存,或许运用Service Worker进行资源的缓存。

在iOS中,能够运用如下代码在WebView中加载本地资源:

let url = Bundle.main.url(forResource: "index", withExtension: "html", subdirectory: "www")
let request = URLRequest(url: url!)
webView.load(request)

在这个示例中,咱们将本地资源放到了www子目录下,而且运用index.html作为入口文件。咱们运用Bundle.main.url(forResource:withExtension:subdirectory:)办法获取到资源的URL,然后创建一个URLRequest并加载到WebView中。

计划三:运用 WebServer

1. 计划描绘

此计划主要是在移动端内置一个小型的 WebServer,经过这个 WebServer 来供给静态资源服务。这样能够让一切的资源恳求都是同源的,避免了跨域问题。同时,也能够方便地办理和更新资源文件。

2. 问题描绘

内置 WebServer 的计划主要的问题是移动设备的功能和资源约束,包含 CPU、内存和电量。别的,WebServer 需求在后台持续运行,这在一些操作系统中或许受到约束。最后,WebServer 的安全性也需求考虑,虽然这是在本地网络环境下,但仍然或许有一些潜在的安全风险。

3. 处理办法

运用 Swift 完结内置 WebServer 的示例代码:

import GCDWebServer
let webServer = GCDWebServer()
webServer?.addDefaultHandler(forMethod: "GET", request: GCDWebServerRequest.self, processBlock: {request in
    let filePath = self.documentPath.appending(request.path)
    let html = try! String(contentsOfFile: filePath, encoding: .utf8)
    return GCDWebServerDataResponse(html: html)
})
webServer?.start(withPort: 8080, bonjourName: nil)

以上代码运用了 GCDWebServer 库来创建一个 HTTP 服务器,监听 8080 端口。addDefaultHandler 函数用来处理一切的 GET 恳求,回来恳求路径下的文件内容。

4. 存在的问题及处理计划

关于功能和资源约束问题,需求在规划和完结时尽量优化功能,削减资源占用。别的,需求处理好运用切换到后台时的情况,或许需求停止 WebServer,或许依据操作系统的规则进行其他处理。关于安全性问题,能够经过一些安全机制,比方只允许本地拜访,或许运用 HTTPS 等办法来进步安全性。

计划四:前端运用Service Worker或WPA的Cache API

1. 计划描绘

该计划是指经过前端技能 Service Worker 或 WPA(Web Progressive Applications)的 Cache API,来阻拦和处理浏览器的网络恳求,到达缓存资源和离线拜访的意图。

2. 问题描绘

当前,HTML5 供给了丰厚的离线存储机制,但怎样有效、灵敏地运用这些机制进行资源办理,保证网页在断网或许网络欠安的情况下也能正常拜访,是本计划需求处理的主要问题。

3. 处理办法

前端需求运用 Service Worker 或 WPA 的 Cache API,进行以下操作:

注册 Service Worker 或 WPA,为运用装置 Cache API。
经过 Service Worker 或 WPA 阻拦网络恳求,判别是否射中缓存。假如射中缓存,则直接回来缓存的资源;不然,向服务器发出恳求,获取资源并存入缓存。
缓存战略的制定和实施。能够依据需求,定制不同的缓存战略,如优先运用缓存、优先获取最新资源等。

4. 存在的问题及处理计划

在运用 Service Worker 或 WPA 的 Cache API 过程中,或许会遇到以下问题:

浏览器兼容性:不是一切的浏览器都完全支持 Service Worker 或 WPA 的 Cache API。关于不支持的浏览器,需求寻找代替计划,如运用其他的缓存技能(例如 localStorage、IndexedDB)或许退化到普通的网络恳求。
缓存办理:假如不留意办理,缓存的资源或许会占用很多的存储空间。因而,需求在适宜的时机整理过期的或许不再需求的缓存。同时,需求处理好版别更新时的缓存替换问题。

下面是运用Service Worker合作Cache API进行离线资源办理的基础代码示例:

首先,咱们需求在主线程的JavaScript中注册Service Worker:

if ('serviceWorker' in navigator) {
  window.addEventListener('load', function() {
    navigator.serviceWorker.register('/sw.js').then(function(registration) {
      console.log('Service Worker 注册成功,scope: ', registration.scope);
    }, function(err) {
      console.log('Service Worker 注册失利: ', err);
    });
  });
}
然后在sw.js中界说Service Worker的装置和激活行为:
javascript
Copy code
var CACHE_NAME = 'my-site-cache-v1';
var urlsToCache = [
  '/',
  '/styles/main.css',
  '/script/main.js'
];
self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(function(cache) {
        console.log('Opened cache');
        return cache.addAll(urlsToCache);
      })
  );
});
self.addEventListener('activate', function(event) {
  var cacheWhitelist = ['my-site-cache-v1', 'blog-posts-cache-v1'];
  event.waitUntil(
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.map(function(cacheName) {
          if (cacheWhitelist.indexOf(cacheName) === -1) {
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});

接下来,咱们界说怎样响应恳求:

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.match(event.request)
      .then(function(response) {
        if (response) {
          return response;
        }
        return fetch(event.request).then(
          function(response) {
            if(!response || response.status !== 200 || response.type !== 'basic') {
              return response;
            }
            var responseToCache = response.clone();
            caches.open(CACHE_NAME)
              .then(function(cache) {
                cache.put(event.request, responseToCache);
              });
            return response;
          }
        );
      })
  );
});

这是根本的 Service Worker 注册和运用 Cache API 的流程,你能够依据自己的需求进行更杂乱的战略定制。需求留意的是,运用 Service Worker 和 Cache API 需求 HTTPS 环境。

最终选定的计划2:

即网页静态化计划,咱们需求前端、后端和iOS端各自协作才干完结。下面我详细介绍各端应该怎样做:

前端:

咱们的目标是将编辑器模块的一切资源打包成一个独自的文件,这个过程需求经过Webpack这样的打包东西来完结。

1.1 分包:

为了使项目愈加模块化和优化加载功能,咱们能够在webpack中运用splitChunks进行代码分包。在webpack.config.js中增加以下装备:

optimization: {
  splitChunks: {
    chunks: 'all',
  },
}
1.2 资源的scheme界说:

关于需求打包的资源,咱们能够经过修正Webpack装备文件,将资源文件的引用路径设置为自界说scheme。

关于Vue项目,你能够在vue.config.js中修正webpack的装备:


chainWebpack: config => {
    config.module
        .rule('images')
        .use('url-loader')
        .tap(options => Object.assign(options, { name: 'myscheme://[name].[ext]' }))
}
iOS端:

iOS端主要使命是监听自界说的scheme,并在收到恳求时回来对应的本地资源。

2.1 监听scheme:

你能够经过WKWebView的WKURLSchemeHandler来监听自界说的scheme:

let webViewConfig = WKWebViewConfiguration()
let customSchemeHandler = MySchemeHandler()
webViewConfig.setURLSchemeHandler(customSchemeHandler, forURLScheme: "myscheme")
let webView = WKWebView(frame: .zero, configuration: webViewConfig)
2.2 处理scheme恳求:

在你的SchemeHandler中,你需求完结WKURLSchemeHandler协议的两个办法:

class MySchemeHandler: NSObject, WKURLSchemeHandler {
    func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
        let url = urlSchemeTask.request.url!
        let resource = url.lastPathComponent
        let resourcePath = Bundle.main.path(forResource: resource, ofType: nil)!
        let resourceUrl = URL(fileURLWithPath: resourcePath)
        let response = HTTPURLResponse(url: url, statusCode: 200, httpVersion: "1.1", headerFields: nil)!
        urlSchemeTask.didReceive(response)
        urlSchemeTask.didReceive(Data(contentsOf: resourceUrl))
        urlSchemeTask.didFinish()
    }
    func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
    }
}
后端:

关于后端,主要的使命或许会是协助前端进行资源文件的打包和发布。详细的作业或许会依据项意图实践需求而定。或许的使命包含:

供给资源文件的服务器托管
协助完结资源文件的版别操控和更新战略
供给API接口供移动端查询最新的资源文件信息

将在计划2中增加跨域问题的处理计划:

1, 资源跨域:运用自界说scheme能够处理资源跨域的问题。如上所述,经过设置自界说scheme,将资源的引用路径重定向到运用本地,避免了跨域的问题。自界说scheme如”myscheme”,在前端打包时,资源的引用路径将会变为”myscheme://[资源名]“,然后在iOS端阻拦这个scheme,回来本地的资源文件。

2, 网络恳求跨域:网络恳求的跨域问题或许需求后端协作处理,或许经过移动端进行JS交互完结。一种或许的办法是在后端设置CORS(跨源资源共享)战略,允许来自特定来源的恳求。另一种办法是经过iOS端的JavaScriptCore或WKScriptMessageHandler与网页进行交互,iOS端接收到JS的恳求后,自行进行网络恳求,然后将结果回来给JS。

在iOS端,能够运用WKUserContentController的add办法来增加音讯处理器,然后在WKScriptMessageHandler协议的userContentController:didReceiveScriptMessage:办法中处理JS的恳求。例如:

let contentController = WKUserContentController()
contentController.add(self, name: "jsHandler")
let config = WKWebViewConfiguration()
config.userContentController = contentController
let webView = WKWebView(frame: .zero, configuration: config)
// WKScriptMessageHandler
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
    if message.name == "jsHandler" {
        // 处理JS的恳求,发送网络恳求,然后将结果经过evaluateJavaScript回来给JS
    }
}

这样,经过JS交互,能够处理网络恳求的跨域问题,但需求留意的是,由于在iOS端进行网络恳求,或许会需求处理一些额定的问题,如证书验证、Cookie办理等。