iOS网络缓存分析

一、缓存的意义

为了提高程序的运行速度,我们使用缓存,对于相同的数据请求,如果不使用缓存那么会造成两种影响:1、浪费用户流量 2、程序响应速度慢。

二、缓存的流程

1、第一次请求服务器时

a、使用服务器的数据展示到UI上
b、将服务器的数据缓存到沙盒中
此时,内存缓存有数据,硬盘缓存也有数据。

2、再次请求数据

a、应用程序没关闭
此时内存缓存有数据,硬盘缓存也有数据,再次请求数据会直接从内存缓存中取出数据。
b、应用程序重新打开
程序重新打开,内存缓存已经被清空,从硬盘缓存中取出数据展示,此时内存缓存也有数据,若再次加载数据,直接调用a步骤。

三、缓存的实现

1、缓存类为NSURLCache,可以做到完全的离线缓存,即在没有网络的情况下打开离线内容,通过自定义实现,将缓存文件放到沙河路径下,缓存空间没有大小限制。
2、NSURLCache拦截不到WKWebView中发出的任何网络请求。所以如果使用WKWebView的话,NSURLCache实现不了离线缓存的功能。


各个缓存策略介绍:

(1)NSURLRequestUseProtocolCachePolicy:NSURLRequest默认的cache policy,使用Protocol协议定义。
(2)NSURLRequestReloadIgnoringCacheData:忽略缓存直接从原始地址下载。
(3)NSURLRequestReturnCacheDataElseLoad:只有在cache中不存在data时才从原始地址下载。
(4)NSURLRequestReturnCacheDataDontLoad:只使用cache数据,如果不存在cache,请求失败;用于没有建立网络连接离线模式;
(5)NSURLRequestReloadIgnoringLocalAndRemoteCacheData:忽略本地和远程的缓存数据,直接从原始地址下载,与NSURLRequestReloadIgnoringCacheData类似。 
(6)NSURLRequestReloadRevalidatingCacheData:验证本地数据与远程数据是否相同,如果不同则下载远程数据,否则使用本地数据。
(7)说明:56苹果暂未实现。

3、设置缓存大小
a、默认情况下,内存是4M ,硬盘为20M,缓存路径为系统设置。

  [[NSURLCache sharedURLCache] setMemoryCapacity:4*1024*1024]。
  [[NSURLCache sharedURLCache] setDiskCapacity:20*1024*1024]。 

b、可以自己初始化缓存对象,然后设置缓存路径。

  NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4*1024*1024 diskCapacity:20*1024*1024 diskPath:path];
  [NSURLCache setSharedURLCache:URLCache];

4、要点解析
a、NSURLCache包括磁盘缓存和内存缓存,磁盘缓存有默认的缓存路径,也可以自己指定缓存路径。
a1):设置自定义路径时需要注意:

设置 NSURLCache 时 diskPath 部分传参数时,注意只需要写文件夹名字即可,不需要写全路径。比如传入 @"123" 就会自动创建 Library/Caches/{bundleid}/123/。
重复设置 NSURLCache 时需要注意:如果先设置的默认路径,之后再设置自定义路径的 NSURLCache 时,如果设置前已经发生网络请求,并且已经在默认路径中写入了数据,那么就会出现错乱:虽然在新自定义路径中保存了cache,但读 cache 还是会从默认路径读取,造成每次都找不到cache而重新请求。
设置自定义路径后,之前默认的 cache 并不会清除。需要手动删除 [[NSURLCache sharedURLCache] removeAllCachedResponses];,记得在设置新缓存之前删除,否则 [NSURLCache sharedURLCache] 返回的就是旧有的缓存。可以升级app时删除,也可以在设置自定义路径时删除,做法参考 MRC 的 setter 操作。

b、系统存储空间不足时,当前的请求不会被缓存,包括之前磁盘的缓存也可能被系统清除掉。
c、如果使用NSURLCache,在应用收到内存警告时,要清空缓存:removeAllCachedResponses。
5、NSURLCache相关API

1.功能方法:
(1)- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request;
返回对应的NSURLRequest缓存的response,如果没有则返回nil。
(2)- (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request;
为特定的NSURLRequest指定缓存对象,并存储。
(3)- (void)removeCachedResponseForRequest:(NSURLRequest *)request;  
移除特定NSURLRequest的cache。
(4)- (void)removeAllCachedResponses;
移除所有的cache。

2.property方法
- (NSUInteger)memoryCapacity;
- (NSUInteger)diskCapacity;
- (void)setMemoryCapacity:(NSUInteger)memoryCapacity;
- (void)setDiskCapacity:(NSUInteger)diskCapacity;
- (NSUInteger)currentMemoryUsage;
- (NSUInteger)currentDiskUsage;

四、自定义NSURLCache

在一些特殊场景,如果要实现自定义的缓存机制,需要子类化NSURLCache,有链各个地方需要重写。
1、重写cachedResponseForRequest:(NSURLRequest *)request,这个会在请求发送前会被调用,从中我们可以判定是否针对此NSURLRequest返回本地数据。如果本地没有缓存就调用下面这条语句:return [super cachedResponseForRequest:request];。
2、重写storeCachedResponse:(NSCachedURLResponse )cachedResponse forRequest:(NSURLRequest )request,我们可以对某一个请求做我们自己的数据保存机制,如果使用系统默认的数据保存机制,则调用[super storeCachedResponse:cachedResponse forRequest:request];
重写示例如下:

- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request{

    //获取URL整体路径

    NSString *urlStringMD5 = [self md5:request.URL.absoluteString];



    //获取缓存文件存储地址

    NSString *filePath = [[self getDocumentPath] stringByAppendingPathComponent:urlStringMD5];



    //如果缓存存在,则返回缓存数据,否则使用系统默认处理

    if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {

        //获取缓存文件路径

        NSData *fileData = [[NSData alloc] initWithContentsOfFile:filePath];

        //根据URL路径,获取媒体类型

        NSString *memiType = [self mimeTypeForPath:request.URL.absoluteString];

        //合成NSCachedURLResponse对象,返回

        NSURLResponse *response = [[NSURLResponse alloc] initWithURL:[request URL]

                                                            MIMEType:memiType

                                               expectedContentLength:[fileData length]

                                                    textEncodingName:nil];

        NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:fileData];

        return cachedResponse;

    }else{

        return [super cachedResponseForRequest:request];

    }

}



- (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request{

    //将服务器返回数据缓存起来

    NSString *urlStringMD5 = [self md5:request.URL.absoluteString];

    NSString *filePath = [[self getDocumentPath] stringByAppendingPathComponent:urlStringMD5];

    [cachedResponse.data writeToFile:filePath atomically:YES];

}

五、使用分析

/*!
@brief 如果本地缓存资源为最新,则使用使用本地缓存。如果服务器已经更新或本地无缓存则从服务器请求资源。

@details

步骤:
1. 请求是可变的,缓存策略要每次都从服务器加载
2. 每次得到响应后,需要记录住 etag
3. 下次发送请求的同时,将etag一起发送给服务器(由服务器比较内容是否发生变化)

@return 图片资源
*/
- (void)getData:(GetDataCompletion)completion {
   NSURL *url = [NSURL URLWithString:kETagImageURL];
   NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:15.0];

   // 发送 etag
   if (self.etag.length > 0) {
       [request setValue:self.etag forHTTPHeaderField:@"If-None-Match"];
   }

   [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {

       // NSLog(@"%@ %tu", response, data.length);
       // 类型转换(如果将父类设置给子类,需要强制转换)
       NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
       NSLog(@"statusCode == %@", @(httpResponse.statusCode));
       // 判断响应的状态码是否是 304 Not Modified (更多状态码含义解释: https://github.com/ChenYilong/iOSDevelopmentTips)
       if (httpResponse.statusCode == 304) {
           NSLog(@"加载本地缓存图片");
           // 如果是,使用本地缓存
           // 根据请求获取到`被缓存的响应`!
           NSCachedURLResponse *cacheResponse =  [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
           // 拿到缓存的数据
           data = cacheResponse.data;
       }

       // 获取并且纪录 etag,区分大小写
       self.etag = httpResponse.allHeaderFields[@"Etag"];

       NSLog(@"%@", self.etag);
       dispatch_async(dispatch_get_main_queue(), ^{
           !completion ?: completion(data);
       });
   }] resume];
}

参考资料

猜你喜欢

转载自blog.csdn.net/xiaoxiaocode/article/details/80006467