为了降低开发和维护成本,很多公司和团队选择了嵌入 Web 页面的方式发布客户端,但一直以来 Web 应用与 Native 应用的在用户体验方面都存在不小的差距,因此如何缩小这种差距就成了一个关键的问题。
通常来说,我们的 Web 应用都拥有许多页面,页面切换也比较频繁,而在页面在加载时,相关的资源诸如 JS、CSS、IMG 等在很多情况下并不会随页面变动,因此我们可以将这部分资源放置在本地,在需要的时候交给页面使用。
资源变动的分析
正如前面所有,不会改变的资源只是所有资源中的一部分,因此我们就需要对资源的变动情况进行分析,以便将不会改变的资源提取出来。
资源拦截的实现
经过了资源变动的分析之后,就需要针对性的对资源进行拦截,资源拦截需要分为两步:
-
实现自己的拦截协议
-
注册自己的拦截协议
拦截协议的实现
要实现拦截协议,需要我们重写下面几个方法:
-
canInitWithRequest
: 是否拦截请求,如果不拦截则会交还系统处理 -
canonicalRequestForRequest
: 原请求 -
startLoading
: 加载请求开始 -
stopLoading
: 加载请求结束
为了更好的举例说明,我假设前面提到的 JS、CSS、IMG 三类资源我们都需要拦截,分别有:
-
bootstrap.min.js
-
bootstrap.min.css
-
icon.png
然后,我们来逐步实现自己的 URLProtocol
:
// MXResourceInterceptURLProtocol.swift
// MXWebViewOptimizeDemo
// Created by Meniny on 10/09/15.
// Copyright © 2015 Meniny. All rights reserved.
//
import Foundation
class MXResourceInterceptURLProtocol: NSURLProtocol {
static var res:Dictionary<String,String> = [:] // 资源文件
static var type:Dictionary<String,String> = [:] // 资源类型
// MARK: - 数据初始化
class func initData(){
MXResourceInterceptURLProtocol.res["bootstrap.min.css"] = "res/css"
MXResourceInterceptURLProtocol.res["bootstrap.min.js"] = "res/js"
MXResourceInterceptURLProtocol.res["icon.png"] = "res/img"
MXResourceInterceptURLProtocol.type["bootstrap.min.css"] = "text/css"
MXResourceInterceptURLProtocol.type["bootstrap.min.js"] = "application/javascript"
MXResourceInterceptURLProtocol.type["icon.png"] = "image/png"
}
// MARK: - 拦截请求
override class func canInitWithRequest(request: NSURLRequest) -> Bool {
if MXResourceInterceptURLProtocol.res.isEmpty {
MXResourceInterceptURLProtocol.initData()
}
let requestUrl = request.URL!.absoluteString
for key in MXResourceInterceptURLProtocol.res.keys{
if requestUrl.hasSuffix(key) {
// true: 使用本协议处理
return true
}
}
// false: 不使用本地协议处理
return false
}
// MARK: - 原请求
override class func canonicalRequestForRequest(request: NSURLRequest) -> NSURLRequest{
return request
}
// MARK: - 替换请求
override func startLoading(){
let path:NSString = self.request.URL!.path!
var name:String!
for key in MXResourceInterceptURLProtocol.res.keys {
if path.hasSuffix(key){
name = key ;
break ;
}
}
if MXResourceInterceptURLProtocol.res.indexForKey(name) == nil{
return;
}
let dir = MXResourceInterceptURLProtocol.res[name]
let urlPath = NSBundle.mainBundle().pathForResource(name, ofType: nil, inDirectory:dir)
let url = NSURL.fileURLWithPath(urlPath!)
let type = MXResourceInterceptURLProtocol.type[name]
let data = NSData(contentsOfURL:url)
let response = NSURLResponse(URL: url, MIMEType: type, expectedContentLength: data!.length, textEncodingName: "UTF-8")
self.client!.URLProtocol(self, didReceiveResponse: response, cacheStoragePolicy: .NotAllowed)
self.client!.URLProtocol(self, didLoadData: data!)
self.client!.URLProtocolDidFinishLoading(self)
}
// MARK: - 加载结束,这里不做特殊处理
override func stopLoading() {
}
}
拦截协议的注册
前面提到,拦截协议实现后还需要进行注册,此操作我们来到 AppDelegate
中进行:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// 注册协议
NSURLProtocol.registerClass(MXResourceInterceptURLProtocol)
// 其他代码...
return true
}
好了,到这里一个基本的资源拦截和替换就完成了。