如果你是一名使用mattt thompson网络框架afnetworking的ios开发者(如果你不是,那还等什么呢?),也许你对这个框架中的缓存机制很好奇或者疑惑,并想学习如何在自己的app中充分利用这种机制。
afnetworking实际上使用了两个独立的缓存机制:
● afimagecache:一个提供图片内存缓存的类,继承自nscache。
● nsurlcache:nsurlconnection's默认的url缓存机制,用于存储nsurlresponse对象:一个默认缓存在内存,通过配置可以缓存到磁盘的类。
为了理解每个缓存系统是如何工作的,我们看一下他们是如何定义的。
afimagecache是如何工作的
afimagecache是uiimageview+afnetworking分类的一部分。它继承自nscache,通过一个url字符串作为它的key(从nsurlrequest中获取)来存储uiimage对象。
afimagecache定义:
<code>@interface afimagecache : nscache <afimagecache></code>
<code>// singleton instantiation :</code>
<code>+ (id <afimagecache>)sharedimagecache { </code>
<code> </code><code>static</code> <code>afimagecache *_af_defaultimagecache = nil; </code>
<code> </code><code>static</code> <code>dispatch_once_t oncepredicate; </code>
<code> </code><code>dispatch_once(&oncepredicate, ^{</code>
<code> </code><code>_af_defaultimagecache = [[afimagecache alloc] init];</code>
<code> </code>
<code> </code><code>// clears out cache on memory warning :</code>
<code> </code><code>[[nsnotificationcenter defaultcenter] addobserverforname:uiapplicationdidreceivememorywarningnotification object:nil queue:[nsoperationqueue mainqueue] usingblock:^(nsnotification * __unused notification) {</code>
<code> </code><code>[_af_defaultimagecache removeallobjects];</code>
<code> </code><code>}];</code>
<code> </code><code>});</code>
<code> </code><code>// key from [[nsurlrequest url] absolutestring] :</code>
<code> </code>
<code> </code><code>static</code> <code>inline</code> <code>nsstring * afimagecachekeyfromurlrequest(nsurlrequest *request) { </code>
<code> </code><code>return</code> <code>[[request url] absolutestring];</code>
<code>}</code>
<code>@implementation afimagecache</code>
<code>// write to cache if proper policy on nsurlrequest :</code>
<code>- (uiimage *)cachedimageforrequest:(nsurlrequest *)request { </code>
<code> </code><code>switch</code> <code>([request cachepolicy]) { </code>
<code> </code><code>case</code> <code>nsurlrequestreloadignoringcachedata: </code>
<code> </code><code>case</code> <code>nsurlrequestreloadignoringlocalandremotecachedata: </code>
<code> </code><code>return</code> <code>nil; </code>
<code> </code><code>default</code><code>: </code>
<code> </code><code>break</code><code>;</code>
<code> </code><code>} </code>
<code> </code><code>return</code> <code>[self objectforkey:afimagecachekeyfromurlrequest(request)];</code>
<code>// read from cache :</code>
<code>- (</code><code>void</code><code>)cacheimage:(uiimage *)image forrequest:(nsurlrequest *)request { </code>
<code> </code><code>if</code> <code>(image && request) {</code>
<code> </code><code>[self setobject:image forkey:afimagecachekeyfromurlrequest(request)];</code>
<code> </code><code>}</code>
afimagecache 从 afnetworking 2.1开始可以进行配置了。有一个公共方法setsharedimagecache。详细文档可以看这里 。它把所有可访问的uiimage对象存到了nscache。当uiimage对象释放之后nscache会进行处理。如果你想观察images什么时候释放,可以实现nscachedelegate的cache:willevictobject方法
nsurlcache如何工作
默认是可以的,但最好还是手动配置一下
既然afnetworking使用nsurlconnection,它利用了原生的缓存机制nsurlcache。nsurlcache缓存了从服务器返回的nsurlresponse对象。
nsurlcache的sharecache方法默认是可以使用的,缓存获取的内容。不幸的是,它的默认配置只是缓存在内存并没有写到硬盘。为了解决这个问题,你可以声明一个 sharedcache,像这样:
<code>nsurlcache *sharedcache = [[nsurlcache alloc] initwithmemorycapacity:2 * 1024 * 1024</code>
<code> </code><code>diskcapacity:100 * 1024 * 1024</code>
<code> </code><code>diskpath:nil];</code>
<code>[nsurlcache setsharedurlcache:sharedcache];</code>
这样,我们声明了一个2m内存,100m磁盘空间的nsurlcache。
对nsurlrequest对象设置缓存策略
nsurlcache对每个nsurlrequest对象都会遵守缓存策略(nsurlrequestcachepolicy)。策略定义如下:
● nsurlrequestuseprotocolcachepolicy:指定定义在协议实现里的缓存逻辑被用于url请求。这是url请求的默认策略
● nsurlrequestreloadignoringlocalcachedata:忽略本地缓存,从源加载
● nsurlrequestreloadignoringlocalandremotecachedata:忽略本地&服务器缓存,从源加载
● nsurlrequestreturncachedataelseload:先从缓存加载,如果没有缓存,从源加载
● nsurlrequestreturncachedatadontload离线模式,加载缓存数据(无论是否过期),不从源加载
● nsurlrequestreloadrevalidatingcachedata存在的缓存数据先确认有效性,无效的话从源加载
用nsurlcache缓存到磁盘
cache-control http header
cache headers的文章。
subclass nsurlcache for ultimate control
如果你想绕过 cache-control 需求,定义你自己的规则来读写一个带有 nsurlresponse对象的nsurlcache,你可以继承 nsurlcache。
这里有个例子,使用 cache_expires 来判断在获取源数据之前对缓存数据保留多长时间。
<code>@interface customurlcache : nsurlcache</code>
<code>static</code> <code>nsstring * </code><code>const</code> <code>customurlcacheexpirationkey = @</code><code>"customurlcacheexpiration"</code><code>;</code>
<code>static</code> <code>nstimeinterval </code><code>const</code> <code>customurlcacheexpirationinterval = 600;</code>
<code>@implementation customurlcache</code>
<code>+ (instancetype)standardurlcache {</code>
<code> </code><code>static</code> <code>customurlcache *_standardurlcache = nil;</code>
<code> </code><code>static</code> <code>dispatch_once_t oncetoken;</code>
<code> </code><code>dispatch_once(&oncetoken, ^{</code>
<code> </code><code>_standardurlcache = [[customurlcache alloc]</code>
<code> </code><code>initwithmemorycapacity:(2 * 1024 * 1024)</code>
<code> </code><code>diskcapacity:(100 * 1024 * 1024)</code>
<code> </code><code>diskpath:nil];</code>
<code> </code><code>return</code> <code>_standardurlcache;</code>
<code>#pragma mark - nsurlcache</code>
<code>- (nscachedurlresponse *)cachedresponseforrequest:(nsurlrequest *)request {</code>
<code> </code><code>nscachedurlresponse *cachedresponse = [super cachedresponseforrequest:request];</code>
<code> </code><code>if</code> <code>(cachedresponse) {</code>
<code> </code><code>nsdate* cachedate = cachedresponse.userinfo[customurlcacheexpirationkey];</code>
<code> </code><code>nsdate* cacheexpirationdate = [cachedate datebyaddingtimeinterval:customurlcacheexpirationinterval];</code>
<code> </code><code>if</code> <code>([cacheexpirationdate compare:[nsdate date]] == nsorderedascending) {</code>
<code> </code><code>[self removecachedresponseforrequest:request];</code>
<code> </code><code>return</code> <code>nil;</code>
<code> </code><code>}</code>
<code> </code><code>return</code> <code>cachedresponse;</code>
<code>- (</code><code>void</code><code>)storecachedresponse:(nscachedurlresponse *)cachedresponse</code>
<code> </code><code>forrequest:(nsurlrequest *)request</code>
<code>{</code>
<code> </code><code>nsmutabledictionary *userinfo = [nsmutabledictionary dictionarywithdictionary:cachedresponse.userinfo];</code>
<code> </code><code>userinfo[customurlcacheexpirationkey] = [nsdate date];</code>
<code> </code><code>nscachedurlresponse *modifiedcachedresponse = [[nscachedurlresponse alloc] initwithresponse:cachedresponse.response data:cachedresponse.data userinfo:userinfo storagepolicy:cachedresponse.storagepolicy];</code>
<code> </code><code>[super storecachedresponse:modifiedcachedresponse forrequest:request];</code>
<code>@end</code>
既然你有了自己的 nsurlcache子类,不要忘了在appdelegate里边初始化并使用它
<code>customurlcache *urlcache = [[customurlcache alloc] initwithmemorycapacity:2 * 1024 * 1024</code>
<code> </code><code>diskcapacity:100 * 1024 * 1024</code>
<code> </code><code>diskpath:nil];</code>
<code>[nsurlcache setsharedurlcache:urlcache];</code>
overriding the nsurlresponse before caching
<code>- (nscachedurlresponse *)connection:(nsurlconnection *)connection</code>
<code> </code><code>willcacheresponse:(nscachedurlresponse *)cachedresponse {</code>
<code> </code><code>nsmutabledictionary *mutableuserinfo = [[cachedresponse userinfo] mutablecopy];</code>
<code> </code><code>nsmutabledata *mutabledata = [[cachedresponse data] mutablecopy];</code>
<code> </code><code>nsurlcachestoragepolicy storagepolicy = nsurlcachestorageallowedinmemoryonly;</code>
<code> </code><code>// ...</code>
<code> </code><code>return</code> <code>[[nscachedurlresponse alloc] initwithresponse:[cachedresponse response]</code>
<code> </code><code>data:mutabledata</code>
<code> </code><code>userinfo:mutableuserinfo</code>
<code> </code><code>storagepolicy:storagepolicy];</code>
<code>// if you do not wish to cache the nsurlcachedresponse, just return nil from the delegate function:</code>
<code> </code><code>return</code> <code>nil;</code>
disabling nsurlcache
不想使用 nsurlcache,可以,只需要将内存和磁盘空间容量设为零就可以了
<code>nsurlcache *sharedcache = [[nsurlcache alloc] initwithmemorycapacity:0</code>
<code> </code><code>diskcapacity:0</code>
总结
我写这篇博客是为了ios社区贡献一份力,总结了一下我在处理关于 afnetworking缓存相关的问题。我们有个内部app加载了好多图片,导致内存问题以及性能问题。我主要职责就是诊断这个app的缓存行为。在这个研究过程中,我在网上搜索了好多资料并且做了好多调试。然后我总结之后写到了这篇博客中。我希望这篇文章能够为其他人用afnetworking的时候提供帮助,真心希望对你们有用处!
外文地址:http://blog.originate.com/blog/2014/02/20/afimagecache-vs-nsurlcache/
来源:tian's blog