Swift中使用NSURLProtocol(NSURL协议)


原文地址:http://www.raywenderlich.com/76735/using-nsurlprotocol-swift
泰然翻译组:独轩。校对:glory。

友情提示:本教程中内容由Zouhair Mahieddine编写,适用于ios8系统swift语言,在xcode6 beta 7下编译通过。由Rocir Santiago整理。

NSURL协议对URL来说就像一把神奇的钥匙。它可以允许你通过定义定制的URL方案和重新定义现有的URL方案的行为来重新定义苹果系统的URL加载系统的操作。

听起来很神奇吧?那是必须的必啊~~。因为,如果仔细观察的话,对URLS就会有一种感受-就如同我们周围无时不在的爱!UIWebView和WKWebView使用的是什么?

URLS.MPMoviePlayer的视频流又是用的什么?URLS。你是怎么将自己的app上传到Itunes,新兴起的FaceTime和Skype?如何在系统中运行一个不存在的软件?在一个网页中载入一张图像?这些都是使用的URLS。看一眼NSFileManager的详细情况吧,你会发现有许多操作方法都需要或者返回一个URLS。

在本篇教程中,您将了解如何定义一个协议处理程序来修改URL方案。它将添加一个简陋的,提前准备好的缓存层,它会在Core data存储检索资源。启用它后,一个普通的UIWebView能够做到在一段时间后离线查看浏览器缓存过的页面。

在正式开始之前,你需要明白或者熟悉一些关于网络的概念,来弄明白NSURLConnection的工作原理。如果你对NSURLConnection的概念并不是很熟悉,那么我建议你看一下这个教程,或者苹果提供的文档

准备好学习如何使用NSURLProtocol了吗?好,自己去整一杯咖啡或者其它此类的东西然后坐到一个软软的长椅上,放松你的头脑一步一步根着做。

正式开始喽

本教程将会带领你做一个简单的手机网页浏览器,正如下面要说的那样的浏览器。

它会有一个简易用来让用户输入网页地址的界面。最变态的地方是你的浏览器需要成功缓存并恢复网页。这样用户才能在一眨眼的工夫后查看已经访问过的网页,因为这些网页不在从网络中获得,而是从程序的本地缓存中读取。

你知道网页加载速度越快,就相当于用户越喜欢,开心,所以,这是一个NSURLProtocol如何提高程序艺术性的好例子。

以下是你将要做的步骤:

·使用UIWebView来显示一个网页。

·使用Core Data来缓存这些网页数据。

如果你对Core Data并不熟悉,可以看一下这个教程。不管怎么样,我感觉这个教程中所使用的所有代码对于学习NSURLProtocol已经是足够了。使用Core Data只是实现本地缓存的一种方法,所以它不是本教程的重点。

工程概述

你可以在这里下载初始工程。下载完成后,解压并打开项目文件。当你打开工程后,主要有2个文件。第一个文件是Main.storyboard文件。他包含了一个UIViewController设定好了你需要实现的类。注意UITextField(用来输入地址),UIButton(用来打开地址请求)和UIWebView视图。

打开BrowserViewController.swift文件。这里你会看到为界面组件设定的基本方法。UIViewController实现了UITextFieldDelegate协议,所以当用户点击确认键后你可以调用相应的请求方法。IBAction方法用来响应按钮事件,如果前面是一样的效果。最后,这个sendRequest()方法获得文本框中的字符串,创建一个NSURL请求对象,并调用了loadRequest()方法,UIWebView随后进行加载显示。

当你熟悉该工程后,编译并运行!程序启动后,在地址栏中输入“http://raywenderlich.com”,然后点击"Go"按钮。程序中的UIWeb视图会加载并显示出请求结果。很简单的一个开始。现在,是时候锻炼下你手指的肌肉了,下一步,敲代码!

网络请求拦截

在IOS系统中有一个用于URL请求的类集URL Loading System.在URL Loading System中最核心的类是NSURL。对于网络请求,该类告知应用程序试图去连接到什么主机并指向那台主机的资源。附加的,在NSURLRequest对象中会添加HTTP头,通信内容,等...加载系统提供了一些不同各类的类用来处理请求,最常用的就是NSURLConnection和NSURLSession.

现在,是时候让我们的程序拦截所有的NSURL的请求了。因为你需要创建并实现你自己的NSURLProtocol。

点击File\new\File...选择IOS\Source\Cocoa Touch Class然后 点击Next按钮。在类名中,输入MyURLProtocol,在子类名称中输入NSURLProtocol。确认一下使用的语言为Swift。最后,点击NEXT,当对象框出现后点击CREATE。

打开MyURLProtocol.swift并用下面的内容替换它:

import UIKit



var requestCount = 0



class MyURLProtocol: NSURLProtocol {

    override class func canInitWithRequest(request: NSURLRequest) -> Bool {

    println("Request #\(requestCount++): URL = \(request.URL.absoluteString)")

        return false

    } 

}

每次URL Loading System收到一个请求就会去加载一个URL,它会寻找一个协议注册句柄来处理请求。每个处理程序会告诉系统是否能处理给定的请求通过canInitWithRequest(_:)方法。

该方法中的参数request如果可以进行操作将会被访问。如果方法返回值为true,那么loading system会依懒NSURLProtocol的子类来处理这个请求,并忽略其它的任何句柄。

如果自定义处理中没有可使用的注册句柄,那么URL Loading System将会使用系统默认的方法来进行处理。

如果你要实现一个新的协议,比如foo://,那么你需要检查一下该请求中的URL方案为foo。但在上面的例子中,你只是直接的返回了一个false,那么你的程序不会去处理任何请求。在过一会儿,你的程序就可以来处理这些请求拉!

NOTE:NSURLProtocol是用一个抽象类来实现的。你可以创建的自定义行为的URL协议,但永远不要直接去实例化一个NSURLProtocol对象。

打开AppDelegate.swift并使用下面的代码替换application(_:didFinishLaunchingWithOptions:)方法:

func application(application: UIApplication,

             didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {

    NSURLProtocol.registerClass(MyURLProtocol)

    return true

}

现在,当你运行你的程序后,它会注册URL Loading System协议。那就意味着他将会有机会来处理每一个请求然后交付给URL Loading System。包括代码直接调用loading system,以及许多系统组件依赖于加载框架的URL,比如UIWebView。

编译并运行工程。在地址栏中输入http://raywenderlich.com网站地址,点击Go按钮,然后查看一下Xcode的控制台输出。现在,对于每一个请求,程序都会输出,URL Loading System会调用你的类如果它可以处理该请求的话。

在控制台输出中,你会看到这些内容:

Request #0: URL = http://raywenderlich.com/

Request #1: URL = http://raywenderlich.com/

Request #2: URL = http://raywenderlich.com/

Request #3: URL = http://raywenderlich.com/

Request #4: URL = http://raywenderlich.com/

Request #5: URL = http://raywenderlich.com/

Request #6: URL = http://www.raywenderlich.com/

Request #7: URL = http://www.raywenderlich.com/

Request #8: URL = http://www.raywenderlich.com/

Request #9: URL = http://www.raywenderlich.com/

Request #10: URL = http://www.raywenderlich.com/

Request #11: URL = http://www.raywenderlich.com/

Request #12: URL = http://raywenderlich.com/

Request #13: URL = http://cdn3.raywenderlich.com/wp-content/themes/raywenderlich/style.min.css?ver=1402962842

Request #14: URL = http://cdn3.raywenderlich.com/wp-content/plugins/swiftype-search/assets/autocomplete.css?ver=3.9.1

Request #15: URL = http://cdn4.raywenderlich.com/wp-content/plugins/videojs-html5-video-player-for-wordpress/plugin-styles.css?ver=3.9.1

Request #16: URL = http://vjs.zencdn.net/4.5/video-js.css?ver=3.9.1

Request #17: URL = http://cdn3.raywenderlich.com/wp-content/themes/raywenderlich/style.min.css?ver=1402962842

...

现在看来,你的类输出了记录请求的URL的字符串的日志并返回了false,也就是说你自定义的类不对处理该请求。但是如果你仔细看一下日志后,你会看到所有的请求都来自UIWebView。他输出了该网站的.html文件和所有的资源文件 ,比如JPEG文件和CSS文件。每次UIWebView需要发送请求的时候,它都会先输出到日志中在它实际发送请求前。计数器变量已经展示出请求的总个数-大概超过500个-原因是该网站中的所有资源。

这是你的实现方式:你的自定义类被每一个URL的请求所调用,下一步,你可以对每一个请求进行一些处理!

自定义URL加载

没有用户说“我喜欢页面一直处于加载状态。”所以,现在你需要确定你的程序真的可以处理这个请求操作。当方法canInitWithRequest(_:)返回true后,那完全表明,你的类的职责就是处理该请求的每个内容。这就意味着你需要得到该请求的数据,然后把它交给URL Loading System。

你该如何获得请求的数据呢?

如果你碰巧实现了一个新的网络协议(比如,添加一个foo://协议),那么这就是你接受实现一个网络协议的严酷乐趣。但是由于你的目标只是插入一个定制高速缓存层,你可以通过使用NSURLConnection获取数据。

实际上你只需要使用NSURLConnection拦截获取到这个请求然后把它传递给本地的URL loading System就可以了。

你的自定义子类NUSRLProtocol实现NSURLProtocolClient协议后可以返回一个数组对象数据。可能有一些困惑一直在你的脑海:NSURLProtocol是一个类,而NSURLProtocolClient是一种协议!

通过客户端端,连接到URL Loading System实现状态改变,响应请求和数据交互。

打开MyURLProtocol.swift然后添加下面的变量到MyURLProtocol类定义的上方。

var connection: NSURLConnection!

下一步,找到canInitWithRequest(_:)方法。将返回值那行的值修改为返回true。

return true

现在添加四个方法:

override class func canonicalRequestForRequest(request: NSURLRequest) -> NSURLRequest {

    return request

}



override class func requestIsCacheEquivalent(aRequest: NSURLRequest,

                               toRequest bRequest: NSURLRequest) -> Bool {

    return super.requestIsCacheEquivalent(aRequest, toRequest:bRequest)

}



override func startLoading() {

    self.connection = NSURLConnection(request: self.request, delegate: self)

}



override func stopLoading() {

    if self.connection != nil {

        self.connection.cancel()

    }

    self.connection = nil

}

上面的内容定义了协议的“标准请求”都包含了什么,至少它应会对相同的请求当返回一个相同标准请求。所以如果两个语义相等(即不一定===)这种内容传入到方法中,那么输出请求也应该语义相等。例如,如果你的自定义URL方案不分大小写,那么你可能会决定,规范化的URL都是小写的。

为了满足这个最低要求,只需要返回请求本身。通常,这是一个可靠的首选解决方案,因为你通常不想更改请求。毕竟,你相信开发人员,对吧?在这里你可能需要做的一件事情可能是通过添加一个头信息来返回一个新的请求。

requestIsCacheEquivalent(_:toRequest:)方法是你花时间来定义当两个自定义URL方案的请求相等时的把这事缓存依据。如果两个请求的内容相同,那么他们应当使用相同的缓存层数据。这是URL Loading System的关注点,内置缓存系统,本教程中暂时不详细说明。在本小节练习中,只需要依靠默认的父类方法来实现。

在加载系统中使用startLoading()和stopLoading() 来通知NSURLProtocol开始或者停止处理请求。在startLoading方法中,将NSURLConnection加载数据进行了实例化。stopLoading方法的存在是为了使URL加载过程可以被中断。上面操作处理了中断当前连接,并将它消毁。

哇!你已经实现了一个有效的NSURLProtocol实例所需要的接口。如果你想了解更多内容的话,查看官方文档这里描述了一个有效的NSURLProtocol的实例可以实现的所有方法。

但量现在你的代码尚未结束!你还需要做的重要工作就是在NSURLConnection中你创建的的回调函数来处理请求。

打开MyURLProtocol.swift然后添加如下方法:

func connection(connection: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {

    self.client!.URLProtocol(self, didReceiveResponse: response, cacheStoragePolicy: .NotAllowed)

}



func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {

    self.client!.URLProtocol(self, didLoadData: data)

}



func connectionDidFinishLoading(connection: NSURLConnection!) {

    self.client!.URLProtocolDidFinishLoading(self)

}



func connection(connection: NSURLConnection!, didFailWithError error: NSError!) {

    self.client!.URLProtocol(self, didFailWithError: error)

}

这些就是NSURLConnection的回调函数。当你使用的NSURLConnection实例进行数据加载,进行响应,有数据变化,完成加载,或者遇到错误的时候就会调用这些方法。在每一种情况下,你都需要在客户端对这些信息进行处理。

所以回顾一下,MyURLProtocol处理并创建自己的NSURLConnection通知处理连接请求。在上面的NSURLConnection回调方法中,协议处理器传送消息给连接URL加载系统。这些信息表示了加载进度,是否完成或者遇到错误。仔细观察你会发现NSURLConnectionDelegate与NSURLProtocolClient之前的消息特征非常相似,它们都是用来处理数据异步加载。当然,还需要注意一下MyURLProtocol 是如何使用它的client 属性来将消息传回到URL Loading System的。

编译并运行工程。当程序启动后,输入之前的网站地址,点击GO按钮。

哇!在浏览器中没有显示出任何内容!如果你在程序运行过程中注意到调试导航,你会发现内容使用已经不受控制。控制台日志应当滚动显示出大量相同的URL请求,是哪里出错了吗?

在控制台日志,你应当会看到下面这种内容消息在无限循环的输出:

Request #0: URL = http://raywenderlich.com/

Request #1: URL = http://raywenderlich.com/

Request #2: URL = http://raywenderlich.com/

Request #3: URL = http://raywenderlich.com/

Request #4: URL = http://raywenderlich.com/

Request #5: URL = http://raywenderlich.com/

Request #6: URL = http://raywenderlich.com/

Request #7: URL = http://raywenderlich.com/

Request #8: URL = http://raywenderlich.com/

Request #9: URL = http://raywenderlich.com/

Request #10: URL = http://raywenderlich.com/

...

Request #1000: URL = http://raywenderlich.com/

Request #1001: URL = http://raywenderlich.com/

你需要回到Xcode并在考虑这个问题前停止程序运行。

解决无限循环标签问题

再次思考一下关于URL LOADING SYSTEM和协议注册,你可能会想到为什么会发生这种现象。当UIWebView将要加载URL的时候,URL Loading System如何可以处理该特殊的请求它就会通知MyURLProtocol。而自定义类中返回了true,表明它可以处理该特殊请求。

所以URL Loading System将会调用startLoading方法创建一个自定义协议的实例。然后你实现的协议创建并连接了它的NSURLConnection。但是它也调用了URL Loading System.猜猜为什么?因为你在canInitWithRequest(_:) 总是返回true,它又创建了另一个新的MyURLProtocol实例。

这个新的实现将导致又创建一个新的实例,接着创建无限数量的实例。这就是你的程序没有加载出任何内容的原因!它占用了大量的内存,只是在控制台输出这个URL。这个欠佳的浏览器被死循环卡住了!你的用户会因为这个问题给予他们机器的损坏而受打击。

很显然在canInitWithRequest(:) 方法中,你不能一直返回true.你需要做一些事情让URL Loading System能够只去处理一次那个请求。解决的办法就是NSURLProtocol接口。找到类中叫做setProperty(:forKey:inRequest:) 的方法,它可以允许你添加给定URL请求的自定义属性。通过这种方法,你可以为该请求添加一个属性来“标记”它,然后浏览器在下次遇到它的时候就不在对它进行处理。

下面是如何打破浏览器死循环的实例。打开MyURLProtocol.swift.然后修改startLoading()和canInitWithRequest(_:)方法为如下代码:

override class func canInitWithRequest(request: NSURLRequest!) -> Bool {

    println("Request #\(requestCount++): URL = \(request.URL.absoluteString)")



    if NSURLProtocol.propertyForKey("MyURLProtocolHandledKey", inRequest: request) != nil {

      return false

    }



    return true

}



override func startLoading() {

    var newRequest = self.request.copy() as NSMutableURLRequest

    NSURLProtocol.setProperty(true, forKey: "MyURLProtocolHandledKey", inRequest: newRequest)



    self.connection = NSURLConnection(request: newRequest, delegate: self)

}

现在startLoading()设定如果给定的请求关联属性值为“MyURLProtocolHandledKey”,那么方法会返回true.也就是说下次再使用给定的NSURLRequest请求调用canInitWithRequest(_:)方法的时候,只有当关联属性被设置的时候才可被访问。

如果被标记过的话,会被设置为true,也就是说下次你就不用在去处理它了。URL Loading System将会从网站中加载数据。当你的MyURLProtocol被实例化后,它将会从NSURLConnectionDelegate接收到回调方法的信息。

编译并运行。当程序运行起来后,网站内容会成功的在网页视图中显示出来。伟大的胜利!控制台的输出内容应当像如下这样:

Request #0: URL = http://raywenderlich.com/

Request #1: URL = http://raywenderlich.com/

Request #2: URL = http://raywenderlich.com/

Request #3: URL = http://raywenderlich.com/

Request #4: URL = http://raywenderlich.com/

Request #5: URL = http://raywenderlich.com/

Request #6: URL = http://raywenderlich.com/

Request #7: URL = http://raywenderlich.com/

Request #8: URL = http://raywenderlich.com/

Request #9: URL = http://www.raywenderlich.com/

Request #10: URL = http://www.raywenderlich.com/

Request #11: URL = http://www.raywenderlich.com/

Request #12: URL = http://raywenderlich.com/

Request #13: URL = http://cdn3.raywenderlich.com/wp-content/themes/raywenderlich/style.min.css?ver=1402962842

Request #14: URL = http://cdn3.raywenderlich.com/wp-content/plugins/swiftype-search/assets/autocomplete.css?ver=3.9.1

Request #15: URL = http://cdn4.raywenderlich.com/wp-content/pluginRse/qvidueeosjts -#h1t6m:l URL = ht5t-pv:i/d/ecodn3.raywenderlich.com/-wppl-acyoenrtent/themes/raywenderlich/-sftoyrl-ew.omridnp.css?vreers=s1/4p0l2u9g6i2n8-4s2t

yles.css?ver=3.9.1

Request #17: URL = http://cdn3.raywenderlich.com/wp-content/themes/raywenderlich/style.min.css?ver=1402962842

Request #18: URL = http://vjs.zencdn.net/4.5/video-js.css?ver=3.9.1

Request #19: URL = http://www.raywenderlich.com/wp-content/plugins/wp-polls/polls-css.css?ver=2.63

Request #20: URL = http://cdn3.raywenderlich.com/wp-content/plugins/swiftype-search/assets/autocomplete.css?ver=3.9.1

Request #21: URL = http://cdn3.raywenderlich.com/wp-content/plugins/swiftype-search/assets/autocomplete.css?ver=3.9.1

Request #22: URL = http://cdn4.raywenderlich.com/wp-content/plugins/powerpress/player.min.js?ver=3.9.1

Request #23: URL = http://cdn4.raywenderlich.com/wp-content/plugins/videojs-html5-video-player-for-wordpress/plugin-styles.css?ver=3.9.1

Request #24: URL = http://cdn4.raywenderlich.com/wp-content/plugins/videojs-html5-video-player-for-wordpress/plugin-styles.css?ver=3.9.1

Request #25: URL = http://cdn3.raywenderlich.com/wp-content/themes/raywenderlich/style.min.css?ver=1402962842

Request #26: URL = http://cdn3.raywenderlich.com/wp-content/plugins/swiftype-search/assets/autocomplete.css?ver=3.9.1

...

你也许会感到惊讶,为何你所做的一切只是让程序回到了刚开始的那种效果。是的,因为你需要为接下来的部分做好充分的准备!现在你已经拥有了URL所有数据的控制权,可以任意进行处理修改。下面开始缓存程序的URL数据。

实现本地缓存

记住本程序的基本需求:给出一个地址请求,程序应当可以一次性的加载网站资源,然后可以对这些内容缓存下来。如果下次在有相同的地址请求,那么程序会直接使用已经缓存好的内容,而不必再次从网络上请求获得。

提示:工程的开始部分已经包括了基本的Core Data模版和堆栈。你需要了解Core Data的详细内容只需要把它当成一个不透明的数据存储;如果你对此感兴趣,请查看苹果官网的 Core Data Programming Guide(Core Data编程指导)

下面来存储程序从网站接收到的信息,无论啥时候只要检索到匹配的就缓存它。打开MyURLProtocol.swift 然后在文件的开始添加导入:

import CoreData

继续,在类的定义中添加下面两个变量:

var mutableData: NSMutableData!

var response: NSURLResponse!

当对服务器的响应进行保存的时候,response变量将会保存你需要的元数据。mutableData变量将会保存connection(_:didReceiveData:)回调方法返回的缓存数据。当连接结束后,你就会缓存完成(数据和元数据)。

然后在类中添加下面的就读:

func saveCachedResponse () {

    println("Saving cached response")



    // 1

    let delegate = UIApplication.sharedApplication().delegate as AppDelegate

    let context = delegate.managedObjectContext!



    // 2

    let cachedResponse = NSEntityDescription.insertNewObjectForEntityForName("CachedURLResponse", inManagedObjectContext: context) as NSManagedObject



    cachedResponse.setValue(self.mutableData, forKey: "data")

    cachedResponse.setValue(self.request.URL.absoluteString, forKey: "url")

    cachedResponse.setValue(NSDate(), forKey: "timestamp")

    cachedResponse.setValue(self.response.MIMEType, forKey: "mimeType")

    cachedResponse.setValue(self.response.textEncodingName, forKey: "encoding")



    // 3

    var error: NSError?

    let success = context.save(&error)

    if !success {

        println("Could not cache the response")

    }

}

下面是方法内容的详细解释:

1。在AppDelegate实例中获得Core Data的NSManagedObjectContext。NSManagedObjectContext是Core Data的接口。

2。创建NSManagedObject的实例,来匹配你在.xcdatamodeld 文件中所见到的数据模型。通过你现有的NSURLResponse和NSMutableData引用来设置它的属性。

3。保存NSManagedObjectContext对象的内容。

现在你已经有办法存储数据了,你需要在某个地方调用该方法。打开MyURLProtocol.swift方法,把NSURLConnection 回调方法中的内容修改如下:

func connection(connection: NSURLConnection!, didReceiveResponse response: NSURLResponse!) {

    self.client!.URLProtocol(self, didReceiveResponse: response, cacheStoragePolicy: .NotAllowed)



    self.response = response

    self.mutableData = NSMutableData()

}



func connection(connection: NSURLConnection!, didReceiveData data: NSData!) {

    self.client!.URLProtocol(self, didLoadData: data)

    self.mutableData.appendData(data)

}



func connectionDidFinishLoading(connection: NSURLConnection!) {

    self.client!.URLProtocolDidFinishLoading(self)

    self.saveCachedResponse()

}

现在程序所获得的数据不在由客户端程序存储,而是由你自己实现的协议类来存储了。编译并运行。程序的结果没有什么大的改变,但现在程序已经可以成功从网站的服务器中接收到数据请求并保存到程序本地的数据库中。

检索缓存请求

终于,是时候开始处理缓存请求并将他们发送给NSURLProtocol的客户端了。打开MyURLProtocol.swift。然后添加下面的方法:

func cachedResponseForCurrentRequest() -> NSManagedObject? {

    // 1

    let delegate = UIApplication.sharedApplication().delegate as AppDelegate

    let context = delegate.managedObjectContext!



    // 2

    let fetchRequest = NSFetchRequest()

    let entity = NSEntityDescription.entityForName("CachedURLResponse", inManagedObjectContext: context)

    fetchRequest.entity = entity



    // 3

    let predicate = NSPredicate(format:"url == %@", self.request.URL.absoluteString!)

    fetchRequest.predicate = predicate



    // 4

    var error: NSError?

    let possibleResult = context.executeFetchRequest(fetchRequest, error: &error) as Array?



    // 5

    if let result = possibleResult {

        if !result.isEmpty {

            return result[0]

        }

    }



    return nil

}

方法解析:

1。获得managedObjectContext,就像在saveCachedResponse()方法中那样。

2。创建一个NSFetchRequest,然后使用它得到叫做CachedURLResponse的实体。这是对象模型中你想检索到的实体。

3。predicate用来将需要获得的请求对象CachedURLResponse对象和URL将要加载的内容进行关联。

4。执行获取到的请求。

5。如果结果不为空,返回第一个结果。

现在来回头看一下startLoading()方法的实现。与其先去从网站上获取一切,不如先检索本地缓存。找到当前实现代码,并使用下面的代码替换:

override func startLoading() {

    // 1

    let possibleCachedResponse = self.cachedResponseForCurrentRequest()

    if let cachedResponse = possibleCachedResponse {

        println("Serving response from cache")



        // 2

        let data = cachedResponse.valueForKey("data") as NSData!

        let mimeType = cachedResponse.valueForKey("mimeType") as String!

        let encoding = cachedResponse.valueForKey("encoding") as String!



        // 3

        let response = NSURLResponse(URL: self.request.URL, MIMEType: mimeType, expectedContentLength: data.length, textEncodingName: encoding)



        // 4

        self.client!.URLProtocol(self, didReceiveResponse: response, cacheStoragePolicy: .NotAllowed)

        self.client!.URLProtocol(self, didLoadData: data)

        self.client!.URLProtocolDidFinishLoading(self)

    } else {

        // 5

        println("Serving response from NSURLConnection")



        var newRequest = self.request.copy() as NSMutableURLRequest

        NSURLProtocol.setProperty(true, forKey: "MyURLProtocolHandledKey", inRequest: newRequest)

        self.connection = NSURLConnection(request: newRequest, delegate: self)

    }

}

方法解析:

1。首先,判断当前请求是否为缓存响应。

2。如果它是缓存响应,那么在缓存对象中得到所有内容数据。

3。创建一个NSURLResponse 对象用来存储数据。

4。将数据返回到客户端。设置客户端的缓存存储策略.NotAllowed ,因为你不想让客户端做任何缓存的相关工作,因为这是你的工作。然后调用URLProtocolDidFinishLoading方法来结束加载。没有任何网络请求--这就是它!

5。如果请求为非缓存响应,那么照常从网络中获得。

再次编译并运行工程。打开一个网站,然后退出应用程序。将你的设置切换到飞行模式(或者,如果你使用的是IOS模拟器,就关闭掉电脑的无线网络连接/拔掉网络)然后再次打开之前的网站。尝试加载之前打开的网站。应当会从缓存中读取并显示出网站所有内容。哇!太棒了!你成功了!

你应当会在控制台中看到许多这样的条目:

Request #22: URL = http://vjs.zencdn.net/4.5/video-js.css?ver=3.9.1

Serving response from cache

这些日志说明请求响应来自你的缓存!

这就是使用本地缓存打开网站。现在你的程序已经能够在网站请求中加载缓存数据和元数据了。用户将会享受到更快的页面加载速度和优越的性能!

什么时候使用NSURLProtocol

你该如何使用NSURLProtocol来使你的程序更加的效率,安全让人瞠目结舌?下面是几个例子:

提供自定义的网络响应请求:

无论你使用什么来制作,比如UIWebView,NSURLConnection 甚至第三方库(比如AFNetworking, MKNetworkKit, 自定义的等,这些都是基于NSURLConnection的)。你可以弄个自定义的,既用于数据也用于元数据。你也许会愿意这样做,如果你是用来进行测试的话。

减少网络请求,使用本地数据:

有时候你可以会认为为程序提供某些需要的数据来进行网络连接是没必要的。NSURLProtocol可以使你的程序在本地缓存或者数据库中进行数据检索。

重定向网络请求:

你曾经望过可以将请求重定向到一个代理服务器——不经过用户来允许而让IOS程序定位?恩,这是可以的!NSURLProtocol 为你提供了你所想要的-控制请求。你可以设定你的程序拦截或者重定向它们到另一个服务器或者代理服务器,或者你任何想想连接的。这是绝对的控制!

改变用户请求代理:

在进行任何网络请求前,你可以决定是否改变它的元数据或者数据。举例来说,你可以改变用户的代理。这对于服务器根据用户代理而改变是很有用的。比如根据用户的使用环境或者客户端语言来返回不同的内容。

使用自定义网络协议:

你也行有自己的网络协议(比如,一些建立于UDP基础上的)。你可以在程序中实现它,或者你也可以选择使用一些其它你喜欢的网络协议库。

不用说,方法是有很多的。在本教程中把所有的都列出来是绝对不可能的(不是不太可能)。你可以在NSURLRequest请求响应前通过重定向NSURLResponse做任何你想要做的。更好的是,创建自定义NSURLResponse。怎么说,你也是个程序员。

NSURLProtocol是很强的,记住它不是一人网络库。它是一个你已经使用库中的附加工具。简而言之,你可以利用NSURLProtocol的优势来完善自己的库。

下一步做什么

这里可以下载到本教程的最终源码。

这个教程包括了一个NSURLProtocol的简单应用,但不要误认为这对缓存来说是一个完整的解决方法。还有许多其它需要实现的内容。实际上,加载系统已经内置了缓存配置,可以去了解一下。本教程的目的就是代你了解一下NSURLProtocol。因为NSURLProtocol 可以修改数据和并且有许多其它的组件,很强大!startLoading方法对你的实现几乎没有什么任何限制。

IETF’s RFC 3986 大多数时候定义URL都像这样“...紧凑的字符序列标识一个抽象的或物理资源...”事实是,URL是自己的迷你语言。它是特定领域语言(DSL)的命名和方向。它也许是世界上特定领域最普及的语言,比如电视机,广播和电视广告,印刷在杂志和在世界各地的商店的招牌上。

NSURLProtocol是一种可以使用各种各样方法的语言。当Twitter想要在苹果系统上实现SPDY protocol协议的时候,HTTP 1.1的优秀继任者,他们就使用了NSURLProtocol。你用它来做什么,决定权在你。NSURLProtocol给可能和灵活性,同时需要一个简单的实现来完成你的目标。

请在本教程的论坛的讨论区随意留下任何的问题或建议。就在右下!

标签: Swift

?>