天天看点

(译)缓存在AFNetworking中是如何工作的?AFImageCache和NSUrlCache给你答案

如果你是一名使用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 &lt;afimagecache&gt;</code>

<code>// singleton instantiation :</code>

<code>+ (id &lt;afimagecache&gt;)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(&amp;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 &amp;&amp; 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:忽略本地&amp;服务器缓存,从源加载

    ● 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(&amp;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

继续阅读