一款HybridAPP,如何实现离线缓存策略?也可以简单的说,你的APP只是一个外壳,里面真正加载的内容是H5。如何优化加载内容的速度?先从表面理解NSURLProtocol。它是一个协议,但它实际上是一个类,继承自NSObject。它的作用是处理特定URL协议的加载。它本身是一个抽象类,提供使用特征URL方案处理URL的基础结构。您可以创建自己的NSURLProtocol子类,以允许您的应用程序支持自定义协议或URL方案。应用程序不应该直接实例化NSURLProtocol子类。下载开始时,系统会创建一个适当的协议对象来响应URL请求。你要做的就是定义你自己的协议,然后在APP启动的时候调用registerClass:让系统知道你的协议。此处注意:您无法在watchOS2及更高版本中自定义URL方案和协议。为了支持特定的自定义请求,你必须定义NSURLRequest或NSMutableURLRequest。让这些自定义对象实现请求,这里需要用到NSURLProtocol的propertyForKey:inRequest:和setProperty:forKey:inRequest,然后可以自定义NSURLResponse类来模拟返回信息。接下来开始UIWebView的离线缓存。UIWebView的离线缓存处理首先我们需要自定义一个NSURLProtocol的子类,在AppDelegate.m中-(BOOL)application:(UIApplication*)applicationdidFinishLaunchingWithOptions:(NSDictionary*)launchOptions{[NSURLProtocolregisterClass:[ZGURLProtocolclass]];returnYES;}登记。所有后续操作都在我们自定义的ZGURLProtocol中执行。先看registerClass的作用:尝试注册一个NSURLProtocol的子类,使其对URLLoadingSystem可见。这里的URLLoadingSystem是一组类和协议,可以让你的应用访问URL产生的内容,比如请求、接收内容、Cache等。当URLLoadSystem开始加载请求时,依次调用每个已注册的协议类,以确定它是否可以用指定的请求进行初始化。第一个被调用的方法是:+(BOOL)canInitWithRequest:(NSURLRequest*)request;在此方法中执行缓存过滤。比如你只想缓存js,那么就判断请求的路径的后缀。如果是js,返回YES。否则返回NO。如果返回YES,则表示请求是由自定义的URLProtocol处理的,不保证所有注册的NSURLProtocol都能被处理。如果你定义了多个NSProtocol子类,这些子类将以相反的顺序被调用。也就是说,如果这样写:[NSURLProtocolregisterClass:[ZGURLProtocolclass]];[NSURLProtocolregisterClass:[ZProtocolclass]];那么最好的执行是ZProtocol,如果引用initWithRequest:返回YES,则请求由ZProtocol处理,并且不会再次采取ZGURLProtocol。如果ZProtocol的initWithRequest:返回NO,则请求继续向下传递,由其他NSURLProtocol子类处理。一旦返回YES,请求将由自己编写的子类处理。首先,它会调用:+(NSURLRequest*)canonicalRequestForRequest:(NSURLRequest*)request这是一个抽象方法,子类必须实现它。一般情况下,我们一般都是直接返回请求,但是这里也可以直接修改请求,包括header、hosts等,可以重定向指定的请求。在这里,我们只返回现有的请求。紧接着,请求将开始:-(void)startLoading;该方法的作用是开始请求协议指定的请求。该方法也是协议子类必须实现的方法。这里做的是:首先判断是否有缓存数据,如果有,自己创建NSURLResponse,然后把缓存数据放进去,在客户端进行一些操作,然后返回;如果没有缓存数据,则创建一个新的NSURLConnection,然后发送请求。先说缓存的情况:.clientURLProtocol:selfdidReceiveResponse:responsecacheStoragePolicy:NSURLCacheStorageAllowed];[self.clientURLProtocol:selfdidLoadData:model.data];[self.clientURLProtocolDidFinishLoading:self];return;}(model为缓存数据)如果有缓存,则使用缓存数据和MIMEtype,然后构建NSURLResponse,然后通过协议客户端调用代理方法。这里的client是一个协议,如下:@protocolNSURLProtocolClient-(void)URLProtocol:(NSURLProtocol*)protocolwasRedirectedToRequest:(NSURLRequest*)requestredirectResponse:(NSURLResponse*)redirectResponse;-(void)URLProtocol:(NSURLValchedResponse*)协议(NSCachedURLResponse*)cachedResponse;-(void)URLProtocol:(NSURLProtocol*)protocoldidReceiveResponse:(NSURLResponse*)responsecacheStoragePolicy:(NSURLCacheStoragePolicy)政策;-(void)URLProtocol:(NSURLProtocol*)protocoldidLoadData:(NSData*idtocoling):(NSData*idtocolingD)URL(NSURLProtocol*)协议;-(void)URLProtocol:(NSURLProtocol*)protocoldidFailWithError:(NSError*)error;-(void)URLProtocol:(NSURLProtocol*)protocoldidReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge)URL-Protochallenge*)(:(NSURLProtocol*)protocoldidCancelAuthenticationChallenge:(NSURLAuthenticationChallenge*)challenge;@end该协议为NSURLProtocol子类提供了与URLLoadingSystem通信的接口。APP必须不执行此协议。如果有缓存就调用回调方法,然后处理。在没有缓存的情况下:实例化一个连接,然后发起请求。当我们收到响应时:-(void)connection:(NSURLConnection*)connectiondidReceiveResponse:(NSURLResponse*)response{self.responseData=[[NSMutableDataalloc]init];self.responseMIMEType=response.MIMEType;[self.clientURLProtocol:selfdidReceiveResponse:responsecacheStoragePolicy:NSURLCacheStorageNotAllowed];}接着接收数据:-(void)connection:(NSURLConnection*)connectiondidReceiveData:(NSData*)data{[self.responseDataappendData:data];[self.clientURLProtocol:selfdidLoadData:data];}收到数据后调用:-(void)connectionDidFinishLoading:(NSURLConnection*)connection{ZGCacheModel*model=[ZGCacheModelnew];model.data=self.responseData;model.MIMEType=self.responseMIMEType;[selfsetMiType:model.MIMETypewithKey:[self.request.URLpath]];//用户默认存储MIMEtype[[ZGUIWebViewCachesharedWebViewCache]setCacheWithKey:self.request.URL.absoluteStringvalue:model];[self.clientURLProtocolDidFinishLoading:self];}这个方法结束后home调用,我们需要在这里缓存请求的数据。这样我们本地就有了指定URL的返回数据。还有一个比较重要的东西这里就不介绍了,那就是[NSURLProtocolpropertyForKey:ZGURLProtocolKeyinRequest:request][NSURLProtocolsetProperty:@YESforKey:ZGURLProtocolKeyinRequest:mutableRequest];+(void)setProperty:(id)valueforKey:(NSString*)keyinRequest:(NSMutableURLRequest*)request;作用是设置并关联指定请求中的特定键值。防止多次调用一个请求。这样我们就完成了UIWebView的离线缓存。这里我封装了一个ZGUIWebViewCache。有兴趣的可以看看。WKWebView的离线缓存处理WKWebView的离线缓存和UIWebView的缓存类似,只是使用WKWebView除了一开始调用NSURLProtocol的canInitWithRequest:方法外,后面的请求好像和NSURLProtocol没有关系。网上说WKWebView的请求是独立的。在这个过程中,所以不要使用NSURLProtocol。这是通过NSURLProtocol+WKWebView类处理的。具体可以参考:ZGWKWebViewCache。其余处理与UIWebView缓存处理类似。以上就是网页离线缓存的实现。