美文网首页
UIWebView内容缓存

UIWebView内容缓存

作者: 迷了jiang | 来源:发表于2016-11-23 15:27 被阅读718次

    URL Loading System
    在说 NSURLProtocol 之前需要对 URL Loading System 进行说明,引用苹果官方文档对 URL Loading System 的一个解释:

    The URL loading system is a set of classes and protocols that allow your app to access content referenced by a URL. At the heart of this technology is the NSURL class, which lets your app manipulate URLs and the resources they refer to.
    
    

    大致的意思就是 URL Loading System 是由一系列的 classProtocol 组成,而我们可以通过这些 classProtocol 来操作相关的 url ,其中处于核心的 class 就是 NSURL
    其中相关的 classProtocol 可以使用官方的一张图来说明:

    URL Loading System

    当然 URL Loading System 是由很多个方面组成的详细的情况可以直接查询苹果的官方文档 URL Loading System

    URL loading system 原生已经支持了http,https,file,ftp,data这些常见协议,当然也允许我们定义自己的protocol去扩展,或者定义自己的协议。当URL loading system通过NSURLRequest对象进行请求时,将会自动创建NSURLProtocol的实例(可以是自定义的)。这样我们就有机会对该请求进行处理。官方文档里面介绍得比较少,下面我们直接看如何自定义NSURLProtocol,解读一下 RNCachingURLProtocol这个开源库的使用和原理。

    首先看一下源码解析

    //初始化
    + (void)initialize
    {
      if (self == [RNCachingURLProtocol class])
      {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
          RNCachingSupportedSchemesMonitor = [NSObject new];
        });
        
        //设置支持的协议类型
        [self setSupportedSchemes:[NSSet setWithObject:@"http"]];
      }
    }
    
    //是否可以处理此次的网络请求 yes 可以  no 丢弃
    + (BOOL)canInitWithRequest:(NSURLRequest *)request
    {
      // 判断是否支持协议类型 和 request是否被处理过(防止递归调用)
      if ([[self supportedSchemes] containsObject:[[request URL] scheme]] &&
          ([request valueForHTTPHeaderField:RNCachingURLHeader] == nil))
      {
        return YES;
      }
      return NO;
    }
    
    //这边可用干你想干的事情。。更改地址,或者设置里面的请求头。。
    + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
    {
      return request;
    }
    
    //设置请求内容的缓存地址
    - (NSString *)cachePathForRequest:(NSURLRequest *)aRequest
    {
      // This stores in the Caches directory, which can be deleted when space is low, but we only use it for offline access
      NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
      NSString *fileName = [[[aRequest URL] absoluteString] sha1];//这里只是一个用SHA1算法将字符串加密的category
    
      return [cachesPath stringByAppendingPathComponent:fileName];
    }
    
    - (void)startLoading
    {
        //判断是否已经缓存过
      if (![self useCache]) {
        NSMutableURLRequest *connectionRequest = 
    #if WORKAROUND_MUTABLE_COPY_LEAK
          [[self request] mutableCopyWorkaround];
    #else
          [[self request] mutableCopy];
    #endif
        // 打一下标记
        [connectionRequest setValue:@"" forHTTPHeaderField:RNCachingURLHeader];
        NSURLConnection *connection = [NSURLConnection connectionWithRequest:connectionRequest
                                                                    delegate:self];
        [self setConnection:connection];
      }
      else {
        //创建缓存对象
        RNCachedData *cache = [NSKeyedUnarchiver unarchiveObjectWithFile:[self cachePathForRequest:[self request]]];
        if (cache) {
          NSData *data = [cache data];
          NSURLResponse *response = [cache response];
          NSURLRequest *redirectRequest = [cache redirectRequest];
          if (redirectRequest) {
              //重复访问了同一个请求
            [[self client] URLProtocol:self wasRedirectedToRequest:redirectRequest redirectResponse:response];
          } else {
              //处理请求
            [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; // we handle caching ourselves.
            [[self client] URLProtocol:self didLoadData:data];
            [[self client] URLProtocolDidFinishLoading:self];
          }
        }
        else {
          [[self client] URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotConnectToHost userInfo:nil]];
        }
      }
    }
    
    //缓存请求的内容 
    - (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response
    {
    // Thanks to Nick Dowell https://gist.github.com/1885821
      if (response != nil) {
          NSMutableURLRequest *redirectableRequest =
    #if WORKAROUND_MUTABLE_COPY_LEAK
          [request mutableCopyWorkaround];
    #else
          [request mutableCopy];
    #endif
          //标记为nil 防止递归调用 canonicalRequestForRequest
        [redirectableRequest setValue:nil forHTTPHeaderField:RNCachingURLHeader];
    
        NSString *cachePath = [self cachePathForRequest:[self request]];
        RNCachedData *cache = [RNCachedData new];
        [cache setResponse:response];
        [cache setData:[self data]];
        [cache setRedirectRequest:redirectableRequest];
        [NSKeyedArchiver archiveRootObject:cache toFile:cachePath];
        [[self client] URLProtocol:self wasRedirectedToRequest:redirectableRequest redirectResponse:response];
        return redirectableRequest;
      } else {
        return request;
      }
    }
    
    #pragma mark - NSURLConnectionDelegate
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
    {
      [[self client] URLProtocol:self didLoadData:data];
      [self appendData:data];
    }
    
    - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
    {
      [[self client] URLProtocol:self didFailWithError:error];
      [self setConnection:nil];
      [self setData:nil];
      [self setResponse:nil];
    }
    
    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
    {
      [self setResponse:response];
      [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];  // We cache ourselves.
    }
    

    注意点:
    每次只能只有一个protocol进行处理,如果有多个自定义protocol,系统将采取你registerClass的倒序进行调用,一旦你需要对这个请求进行处理,那么接下来的所有相关操作都需要这个protocol进行管理。
    一定要注意标记请求,不然你会无限的循环下去。。。因为一旦你需要处理这个请求,那么系统会创建你这个protocol的实例,然后你自己又开启了connection进行请求的话,又会触发URL Loading system的回调。系统给我们提供了+ (void)setProperty:(id)value forKey:(NSString *)key inRequest:(NSMutableURLRequest *)request;和+ (id)propertyForKey:(NSString *)key inRequest:(NSURLRequest *)request;这两个方法进行标记和区分。
    大家在使用的时候只需要在Appdelegate注册一下,然后缓存路径自己处理一下就可以了。

    [NSURLProtocol registerClass:[RNCachingURLProtocol class]];
    

    欢迎讨论!

    相关文章

      网友评论

          本文标题:UIWebView内容缓存

          本文链接:https://www.haomeiwen.com/subject/evippttx.html