美文网首页网络
iOS网络请求缓存 - NSURLCache

iOS网络请求缓存 - NSURLCache

作者: 天下林子 | 来源:发表于2018-09-27 09:44 被阅读43次

NSURLCache

NSURLCache为应用程序的URL请求提供复合的内存和磁盘缓存机制。作为Foundation的URL加载系统的一部分,任何加载的请求NSURLConnection都将由NSURLCache完成
网络缓存减少了需要对服务器发出的请求数量,并改善了脱机使用应用程序或在慢速网络条件下使用应用程序的体验。
当请求从服务器加载完其响应后,缓存的响应将保存在本地。下次发出相同的请求时,将立即返回本地缓存的响应,而不连接到服务器。自动且透明地NSURLCache返回缓存的响应。

NSURLCache提供的是内存以及磁盘的综合缓存机制,使用NSURLCache之前需要在AppDelegate中缓存空间的设置:

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024
                                                       diskCapacity:20 * 1024 * 1024
                                                           diskPath:nil];
  [NSURLCache setSharedURLCache:URLCache];
}

缓存策略
NSURLRequestCachePolicy
NSURLRequest有一个属性,它根据以下常量指定请求的缓存行为:cachePolicy

  • NSURLRequestUseProtocolCachePolicy:协议实现中定义的缓存逻辑用于特定的URL加载请求。这是默认策略。
  • NSURLRequestReloadIgnoringLocalCacheData:应从原始来源加载数据。不应使用现有的缓存数据。
  • NSURLRequestReloadIgnoringLocalAndRemoteCacheData:不仅应该忽略本地缓存数据,而且应该指示代理和其他中间体在协议允许的情况下忽略它们的缓存。
  • NSURLRequestReturnCacheDataElseLoad:应使用现有的缓存数据,无论其年龄或到期日期如何。如果对应于请求的高速缓存中没有现有数据,则从始发源加载数据。
  • NSURLRequestReturnCacheDataDontLoad:应使用现有缓存数据,无论其年龄或到期日期如何。如果对应于请求的高速缓存中没有现有数据,则不尝试从始发源加载数据,并且认为负载已经失败(即“离线”模式)。
  • NSURLRequestReloadRevalidatingCacheData:如果源源确认其有效性,则可以使用现有的高速缓存数据,否则从原始源加载URL。

UseProtocolCachePolicy 默认行为
ReloadIgnoringLocalAndRemoteCacheData 不要使用缓存
ReturnCacheDataElseLoad 使用缓存(无论过时),或者如果不存在缓存响应,请从网络加载
ReturnCacheDataDontLoad 离线模式:使用缓存(无论过时),但不要从网络加载
ReloadRevalidatingCacheData 使用前验证服务器的缓存

如果本地没有缓存数据,则进行网络请求。如果本地有缓存,并且缓存没有失效,则使用缓存。如果缓存已经失效,则询问服务器数据是否改变,如果没改变,依然使用缓存,如果改变了则请求新数据。如果没有指定是否失效,那么系统将自己判断缓存是否失效。(通常认为是6-24小时的有效时间)
默认情况下NSURLCache的缓存策略是根据http协议来的,服务器通过Cache-Control: max-age字段来告诉NSURLCache是否需要缓存数据

HTTP缓存语义

因为NSURLConnection旨在支持多种协议 - 包括两者FTP和HTTP/ HTTPS- URL加载系统API以协议无关的方式指定缓存。出于本文的目的,将根据HTTP语义来解释缓存。

HTTP请求和响应使用标头来传递元数据,如字符编码,MIME类型和缓存指令。

请求缓存标头

默认情况下,NSURLRequest将使用当前时间来确定是否应返回缓存的响应。要获得更精确的缓存控制,可以指定以下标头:

  • If-Modified-Since- 此请求标头对应于Last-Modified响应标头。将this的Last-Modified值设置为从上次请求到同一端点的值。
  • If-None-Match- 此请求标头对应于Etag响应标头。使用Etag先前收到的对该端点的最后一个请求的值。

响应缓存标头
一个NSHTTPURLResponse包含一组HTTP标头,其中可以包含以下有关如何缓存该响应的指令:

  • Cache-Control - 此标头必须存在于服务器的响应中,以启用客户端的HTTP缓存。此标头的值可能包括类似其信息max-age(缓存响应的时间长度),以及响应是否可以缓存public或private访问,或者no-cache(根本不)。有关完整详细信息,请参阅Cache-ControlRFC 2616部分。
    除此之外Cache-Control,服务器还可以发送可用于根据需要有条件地请求信息的附加标头(如上一节所述):

  • Last-Modified - 此标头的值对应于上次更改所请求资源的日期和时间。例如,如果客户端请求最近照片的时间线/photos/timeline,则Last-Modified 可以将值设置为拍摄最近照片的时间。

  • Etag - “实体标签”的缩写,这是表示请求内容资源的标识符。实际上,Etag标头值可能类似于MD5资源属性的摘要。这对于可能没有明显Last-Modified价值的动态生成的资源特别有用。

NSURLConnectionDelegate

收到服务器响应后,NSURLConnection委托人就有机会指定缓存的响应。-connection:willCacheResponse:

NSCachedURLResponse是一个包含与响应关联NSURLResponse的缓存的类NSData。

在,该对象已根据URL连接的结果自动创建。因为没有可变对应物,为了改变任何东西,必须构造一个新对象,将任何修改后的值传递给,例如:-connection:willCacheResponse:cachedResponseNSCachedURLResponsecachedResponse–initWithResponse:data:userInfo:storagePolicy:

- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
                  willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
    NSMutableDictionary *mutableUserInfo = [[cachedResponse userInfo] mutableCopy];
    NSMutableData *mutableData = [[cachedResponse data] mutableCopy];
    NSURLCacheStoragePolicy storagePolicy = NSURLCacheStorageAllowedInMemoryOnly;

    // ...

    return [[NSCachedURLResponse alloc] initWithResponse:[cachedResponse response]
                                                    data:mutableData
                                                userInfo:mutableUserInfo
                                           storagePolicy:storagePolicy];
}

如果返回,则不会缓存响应。-connection:willCacheResponse:nil

- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
                  willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
    return nil;
}

如果没有实现,NSURLConnection只会使用否则将传入的缓存响应,因此除非您需要更改或阻止缓存,否则不需要在委托中实现此方法。-connection:willCacheResponse:

API

@property(class, strong) NSURLCache *sharedURLCache

返回NSURLCache的单例,这是一个类属性;
没有特殊缓存要求或约束的应用可以直接使用此实例。 具有更多特定需求的应用可以创建一个自定义的NSURLCache对象,并调用setSharedURLCache:方法将其设置为共享缓存实例。 所有对共享实例的调用都应该在此设置之后。

//Creating a new cache object
- (instancetype)initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger)diskCapacity diskPath:(NSString *)path

使用指定的值来初始化NSURLCache对象;

memoryCapacity: 内存缓存的字节数;
diskCapacity: 磁盘缓存的字节数;
path:在macOS中,path是存储磁盘缓存的位置。在iOS中,path是应用程序的默认缓存目录的子目录的名称,用于存储磁盘缓存(如果不存在则创建子目录)。

生成的NSURLCache支持磁盘存取,因此开发人员在选择此类缓存的容量时可以更加宽松。 在大多数情况下,以几十兆字节的磁盘缓存应该是可以接受的。


//Getting and storing cached objects
- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request

返回缓存的指定request的的响应数据;如果不存在返回nil;

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

缓存指定的request对应的cachedResponse


//Removing cached objects
- (void)removeAllCachedResponses

删除所有的缓存数据;

- (void)removeCachedResponseForRequest:(NSURLRequest *)request

清空指定request对应的缓存的响应数据,如果本来就没有则什么都不做;


//Getting and setting on-disk cache properties
@property(readonly) NSUInteger currentDiskUsage

调用者的磁盘缓存的当前已使用的大小(以字节为单位)。


@property NSUInteger diskCapacity

调用者的磁盘缓存的总容量(以字节为单位)。


//Getting and setting in-memory cache properties
@property(readonly) NSUInteger currentMemoryUsage 和 @property NSUInteger memoryCapacity

同上;


//Instance Methods
- (void)getCachedResponseForDataTask:(NSURLSessionDataTask *)dataTask completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler

这个是写在NSURLSessionTaskAdditions分类中的方法,没有注释和相关文档,下面的方法也是一样的。看方法名,作用应该是取出指定任务中的所有缓存的响应数据;


- (void)removeCachedResponseForDataTask:(NSURLSessionDataTask *)dataTask

删除指定任务中的缓存数据;


- (void)removeCachedResponsesSinceDate:(NSDate *)date

删除指定时间内的缓存数据;


- (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forDataTask:(NSURLSessionDataTask *)dataTask

存储指定任务的响应数据;

测试代码如下:

//
//  MOBUrlCache.h
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface MOBUrlCache : NSURLCache

@end

NS_ASSUME_NONNULL_END



#import "MOBUrlCache.h"

@implementation MOBUrlCache

/*
 
 注意:NSURLCache缓存不缓存request及response,不是由request得缓存策略决定的,缓存策略只是说明是否加载已经存在的缓存,缓存机制有URL Loading System提供。
 */

// CusURLCache告诉系统我有怎样的已缓存的NSCachedURLResponse对象(或者没有),这里拦截了http://img1.gtimg.com/news/pics/hv1/138/183/2205/143426928.jpeg,并且做了假的NSURLResponse以供缓存
- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request
{
    
    
    
    NSURL *url = [request URL];
    NSLog(@"+++++++++++request.string = %@",request.URL.absoluteString);
    
    if ([url.absoluteString isEqualToString:@"http://img1.gtimg.com/news/pics/hv1/138/183/2205/143426928.jpeg"]) {
        
        NSLog(@"+++++++++request = %@",request);
        
        NSURLResponse *response =
        [[NSURLResponse alloc] initWithURL:url
                                  MIMEType:@"text/plain"
                     expectedContentLength:1
                          textEncodingName:nil];
        
        NSCachedURLResponse *cachedResponse =
        [[NSCachedURLResponse alloc] initWithResponse:response
                                                 data:[NSData dataWithBytes:" " length:1]];
        // 有人可能会认为只需要返回假的响应对象就够了,没必要缓存它。但这样会因响应对象被系统释放而导致app crash。不知道为何为会这样,可能是iOS的bug(Mac OS X 10.5.x也存在同样问题,而10.4.x及更早的系统上没有问题),也可能是URL Loading System内部类之间的依赖所致。
        [super storeCachedResponse:cachedResponse forRequest:request];
    }
    
    return [super cachedResponseForRequest:request];
    
}

- (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request
{
    
    NSLog(@"+++++request2 = %@ === +++response2 = %@",request,cachedResponse);
    
    [super storeCachedResponse:cachedResponse forRequest:request];
    
}

@end

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
{
   NSLog(@"-----默认缓存-------%lu", (unsigned long)[NSURLCache sharedURLCache].memoryCapacity);
    NSLog(@"------默认缓存-----%lu", (unsigned long)[NSURLCache sharedURLCache].diskCapacity);
    
    MOBUrlCache *cache = [[MOBUrlCache alloc] initWithMemoryCapacity:(2*1024*1024) diskCapacity:((100 * 1024 * 1024)) diskPath:nil];
    [MOBUrlCache setSharedURLCache:cache];
    
    NSLog(@"------设置缓存---->>>---%lu", (unsigned long)cache.diskCapacity);
    NSLog(@"------设置缓存---->>>---%lu", (unsigned long)cache.memoryCapacity);
    
}


2018-09-26 20:59:35.195512+0800 MOBFrameWorkTest[35355:1380494] -----默认缓存-------512000
2018-09-26 20:59:35.195650+0800 MOBFrameWorkTest[35355:1380494] ------默认缓存-----10000000
2018-09-26 20:59:35.196919+0800 MOBFrameWorkTest[35355:1380494] ------设置缓存---->>>---104857600
2018-09-26 20:59:35.197012+0800 MOBFrameWorkTest[35355:1380494] ------设置缓存---->>>---2097152
  

PS:NSURLCache缓存不缓存request及response,不是由request得缓存策略决定的,缓存策略只是说明是否加载已经存在的缓存,缓存机制有URL Loading System提供。

参考链接:
https://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13
http://petersteinberger.com/blog/2012/nsurlcache-uses-a-disk-cache-as-of-ios5/
https://nshipster.cn/nsurlcache/

相关文章

网友评论

    本文标题:iOS网络请求缓存 - NSURLCache

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