iOS AFNetworking 数据缓存

How Does Caching Work in AFNetworking? : AFImageCache & NSUrlCache Explained

FEB 20TH, 2014

If you are an iOS developer using  Mattt Thompson’s ‘delightful networking framework’ AFNetworking (and
if you aren’t, what are you waiting for?), perhaps you have been been curious or confused about the caching mechanism employed and how you can tweak it to your advantage.

AFNetworking actually
takes advantage of 2 separate caching mechanisms:

  • AFImagecache: a memory-only image
    cache private to AFNetworking,
    subclassed off of NSCache
  • NSURLCache: NSURLConnection's default
    URL caching mechanism, used to store NSURLResponse objects
    : an in-memory cache by default, configurable as an on-disk
    persistent cache

In order to understand how each caching system works, let’s look at how they are defined:

How AFImageCache Works

AFImageCache is
a part of the UIImageView+AFNetworking category.
It is a subclass of NSCache,
storing UIImage objects
with a URL string as its key (obtained from an input NSURLRequest object).

AFImageCache definition:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@interface AFImageCache : NSCache <AFImageCache>

// singleton instantiation :

+ (id <AFImageCache>)sharedImageCache {
    static AFImageCache *_af_defaultImageCache = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _af_defaultImageCache = [[AFImageCache alloc] init];

// clears out cache on memory warning :

    [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidReceiveMemoryWarningNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * __unused notification) {
        [_af_defaultImageCache removeAllObjects];
    }];
});

// key from [[NSURLRequest URL] absoluteString] :

static inline NSString * AFImageCacheKeyFromURLRequest(NSURLRequest *request) {
    return [[request URL] absoluteString];
}

@implementation AFImageCache

// write to cache if proper policy on NSURLRequest :

- (UIImage *)cachedImageForRequest:(NSURLRequest *)request {
    switch ([request cachePolicy]) {
        case NSURLRequestReloadIgnoringCacheData:
        case NSURLRequestReloadIgnoringLocalAndRemoteCacheData:
            return nil;
        default:
            break;
    }

    return [self objectForKey:AFImageCacheKeyFromURLRequest(request)];
}

// read from cache :

- (void)cacheImage:(UIImage *)image
        forRequest:(NSURLRequest *)request {
    if (image && request) {
        [self setObject:image forKey:AFImageCacheKeyFromURLRequest(request)];
    }
}

AFImageCache is
a private implementation of NSCache.
There is no customization that you can do outside of editing the implementation in the the UIImageView+AFNetworking category,
directly. It stores all accessed UIImage objects
into its NSCache. The NSCache controls
when the UIImageobjects
are released. If you wish to observe when images are released, you can implement NSCacheDelegate’s cache:willEvictObject method.

Edit (03.14.14) : Mattt Thompson has gratiously informed me that as of AFNetworking 2.1, AFImageCache is
configurable. There is now a public setSharedImageCache method.
Here’s the full AFN 2.2.1 UIImageView+AFNetworking
specification
.

How NSURLCache Works

Since AFNetworking uses NSURLConnection,
it takes advantage of its native caching mechanism, NSURLCacheNSURLCache caches NSURLResponseobjects
returned by server calls via NSURLConnection.

Enabled by Default, but Needs a Hand

An NSURLCache sharedCache is
enabled by default and will be used by any NSURLConnection objects
fetching URL contents for you.

Unfortunately, it has a tendency to hog memory and does not write to disk in its default configuration. To tame the beast and potentially add some persistance, you can simply declare a shared NSURLCache in
your app delegate like so:

1
2
3
4
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:2 * 1024 * 1024
                                              diskCapacity:100 * 1024 * 1024
                                              diskPath:nil];
[NSURLCache setSharedURLCache:sharedCache];

Here we declare a shared NSURLCache with
2mb of memory and 100mb of disk space

Setting the Cache Policy on NSURLRequest Objects

NSURLCache will
respect the caching policy (NSURLRequestCachePolicy)
of each NSURLRequest object.
The policies are defined as follows :

  • NSURLRequestUseProtocolCachePolicy: specifies that the caching logic defined in the protocol
    implementation, if any, is used for a particular URL load request. This is the default policy for URL load requests
  • NSURLRequestReloadIgnoringLocalCacheData: ignore the local cache, reload from source
  • NSURLRequestReloadIgnoringLocalAndRemoteCacheData: ignore local & remote caches, reload from
    source
  • NSURLRequestReturnCacheDataElseLoad: load from cache, else go to source.
  • NSURLRequestReturnCacheDataDontLoad: offline mode, load cache data regardless of expiration,
    do not go to source
  • NSURLRequestReloadRevalidatingCacheData: existing cache data may be used provided the origin
    source confirms its validity, otherwise the URL is loaded from the origin source.

Caching to Disk with NSURLCache

Cache-Control HTTP Header

Either the Cache-Control header
or the Expires header
MUST be in the HTTP response header from the server in order for the client to cache it (with the existence of the Cache-Control header
taking precedence over the Expires header).
This is a huge gotcha to watch out for. Cache
Control
 can have parameters defined such as max-age (how
long to cache before updating response), public / private access, or no-cache (don’t
cache response). Here is
a good introduction to HTTP cache headers.

Subclass NSURLCache for Ultimate Control

If you would like to bypass the requirement for a Cache-Control HTTP
header and want to define your own rules for writing and reading the NSURLCache given
an NSURLResponse object,
you can subclass NSURLCache.

Here is an example that uses a CACHE_EXPIRES value
to judge how long to hold on to the cached response before going back to the source:

(Thanks to Mattt Thompson for the feedback and code edits!)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@interface CustomURLCache : NSURLCache

static NSString * const CustomURLCacheExpirationKey = @"CustomURLCacheExpiration";
static NSTimeInterval const CustomURLCacheExpirationInterval = 600;

@implementation CustomURLCache

+ (instancetype)standardURLCache {
    static CustomURLCache *_standardURLCache = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _standardURLCache = [[CustomURLCache alloc]
                                 initWithMemoryCapacity:(2 * 1024 * 1024)
                                 diskCapacity:(100 * 1024 * 1024)
                                 diskPath:nil];
    }

    return _standardURLCache;
}

#pragma mark - NSURLCache

- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request {
    NSCachedURLResponse *cachedResponse = [super cachedResponseForRequest:request];

    if (cachedResponse) {
        NSDate* cacheDate = cachedResponse.userInfo[CustomURLCacheExpirationKey];
        NSDate* cacheExpirationDate = [cacheDate dateByAddingTimeInterval:CustomURLCacheExpirationInterval];
        if ([cacheExpirationDate compare:[NSDate date]] == NSOrderedAscending) {
            [self removeCachedResponseForRequest:request];
            return nil;
        }
    }
}

    return cachedResponse;
}

- (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse
                 forRequest:(NSURLRequest *)request
{
    NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:cachedResponse.userInfo];
    userInfo[CustomURLCacheExpirationKey] = [NSDate date];

    NSCachedURLResponse *modifiedCachedResponse = [[NSCachedURLResponse alloc] initWithResponse:cachedResponse.response data:cachedResponse.data userInfo:userInfo storagePolicy:cachedResponse.storagePolicy];

    [super storeCachedResponse:modifiedCachedResponse forRequest:request];
}

@end

Now that you have your NSURLCache subclass,
don’t forget to initialize it in your AppDelegate in order to use it :

1
2
3
4
CustomURLCache *URLCache = [[CustomURLCache alloc] initWithMemoryCapacity:2 * 1024 * 1024
                                                   diskCapacity:100 * 1024 * 1024
                                                                 diskPath:nil];
[NSURLCache setSharedURLCache:URLCache];

Overriding the NSURLResponse before caching

The -connection:willCacheResponse delegate
is a place to intercept and edit the NSURLCachedResponse object
created by NSURLConnection before
it is cached. In order to edit the NSURLCachedResponse,
return an edited mutable copy as follows (code from NSHipster blog):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (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];
}

// If you do not wish to cache the NSURLCachedResponse, just return nil from the delegate function:

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

Disabling NSURLCache

Don’t want to use the NSURLCache?
Not Impressed? That’s okay. To disable the NSURLCache,
simply zero out memory and disk space in the shared NSURLCache definition
in your appDelegate:

1
2
3
4
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0
                                              diskCapacity:0
                                              diskPath:nil];
[NSURLCache setSharedURLCache:sharedCache];

Summary

I wanted to write this blog post for the benefit of the iOS community, to summarize all of the information I found dealing with caching releated to AFNetworking.
We had an internal app loading a lot of images that had some memory issues and performance problems. I was tasked with trying to diagnose the caching behavior of the app. During this exercise, I discovered the information on this post through scouring the
web and doing plenty of debugging and logging. It is my hope that this post summarizes my findings and provides an opportunity for others with AFNetworking experience
to add additional information. I hope that you have found this helpful.

时间: 2024-08-29 15:43:09

iOS AFNetworking 数据缓存的相关文章

iOS AFNetworking 数据缓存(3)

AFNetWorking 在IOS开发中是一个经常会用的第三方开源库,其最好处是维护及时,源码开源.  常用GET与POST请求方法: POST请求:  ? 1 2 3 4 5 6 7 8 9 //初始化一个请求对象  AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];   NSString * url = @"你的请求地址";   //dic 为参数字典  [manager

ios做数据缓存后,显示数据的逻辑是怎样的?

问题描述 ios做数据缓存后,显示数据的逻辑是怎样的? ios做数据缓存后,显示数据的逻辑是怎样的?因为有上拉加载更多,和下拉刷新,求思路 解决方案 iOS数据缓存iOS开发数据缓存玩转iOS开发 - 数据缓存 解决方案二: 1.获取服务器数据后,可以写入本地文件中,需要用到的时候再去读取就可以了: 2.分页的话类似的: 解决方案三: 每次加载页面都可以先从缓存里面读取,读不到再网络获取

iOS中的NSURLCache数据缓存类用法解析_IOS

 在IOS应用程序开发中,为了减少与服务端的交互次数,加快用户的响应速度,一般都会在IOS设备中加一个缓存的机制.使用缓存的目的是为了使用的应用程序能更快速的响应用户输入,是程序高效的运行.有时候我们需要将远程web服务器获取的数据缓存起来,减少对同一个url多次请求.下面将介绍如何在IOS设备中进行缓存.  内存缓存我们可以使用sdk中的NSURLCache类.NSURLRequest需要一个缓存参数来说明它请求的url何如缓存数据的,我们先看下它的CachePolicy类型.    1.NS

iOS网络编程之六——数据缓存类NSURLCache使用解析

iOS网络编程之六--数据缓存类NSURLCache使用解析 一.引言         在前面博客中,介绍了NSURLRequest请求类的相关使用方法,其中有介绍关于请求返回数据的缓存策略,实际上,iOS中具体缓存操作的管理是由NSURLCache类来实现的.NSURLRequest类介绍的博客地址如下: iOS中NSURLRequest相关使用:http://my.oschina.net/u/2340880/blog/620225. 二.NSURLCache中方法与属性 ? 1 2 3 4

iOS开发网络篇—数据缓存

iOS开发网络篇-数据缓存 一.关于同一个URL的多次请求 有时候,对同一个URL请求多次,返回的数据可能都是一样的,比如服务器上的某张图片,无论下载多少次,返回的数据都是一样的. 上面的情况会造成以下问题 (1)用户流量的浪费 (2)程序响应速度不够快 解决上面的问题,一般考虑对数据进行缓存. 二.缓存 为了提高程序的响应速度,可以考虑使用缓存(内存缓存\硬盘缓存) 第一次请求数据时,内存缓存中没有数据,硬盘缓存中没有数据. 缓存数据的过程 当服务器返回数据时,需要做以下步骤 (1)使用服务器

iOS - LocalCache 本地数据缓存

1.自定义方式本地数据缓存 1.1 自定义缓存 1 沙盒路径下的 Library/Caches 用来存放缓存文件,保存从网络下载的请求数据,后续仍然需要继续使用的文件,例如网络下载的离线数据,图片,视频文件等.该目录中的文件系统不会自动删除,可以做离线访问.它的存放时间比 tmp 下的长,但是不如 Library 下的其它目录.总的来说 Caches 目录下存放的数据不能是应用程序运行所必需的,但是能提高应用访问性能的.可写入应用支持文件,保存应用程序再次启动需要的信息.iTunes 不会对这个

iOS 数据库离线缓存思路和网络层封装

一直想总结一下关于iOS的离线数据缓存的方面的问题,然后最近也简单的对AFN进行了再次封装,所有想把这两个结合起来写一下.数据展示型的页面做离线缓存可以有更好的用户体验,用户在离线环境下仍然可以获取一些数据,这里的数据缓存首选肯定是SQLite,轻量级,对数据的存储读取相对于其他几种方式有优势,这里对AFN的封装没有涉及太多业务逻辑层面的需求,主要还是对一些方法再次封装方便使用,解除项目对第三方的耦合性,能够简单的快速的更换底层使用的网络请求代码.这篇主要写离线缓存思路,对AFN的封装只做简单的

OS开发网络篇—数据缓存

iOS开发网络篇-数据缓存 一.关于同一个URL的多次请求 有时候,对同一个URL请求多次,返回的数据可能都是一样的,比如服务器上的某张图片,无论下载多少次,返回的数据都是一样的. 上面的情况会造成以下问题 (1)用户流量的浪费 (2)程序响应速度不够快 解决上面的问题,一般考虑对数据进行缓存.    二.缓存 为了提高程序的响应速度,可以考虑使用缓存(内存缓存\硬盘缓存) 第一次请求数据时,内存缓存中没有数据,硬盘缓存中没有数据. 缓存数据的过程 当服务器返回数据时,需要做以下步骤 (1)使用

mysql-请教一个ios关于数据访问问题

问题描述 请教一个ios关于数据访问问题 asp.net用sql [php 和java用mysql 那ios对应使用什么数据呢? 解决方案 ios现在主流是用 Sqlite,sql语句和其它一样 解决方案二: 用sqlite,你可以试试用FMDB这些封装好的框架. 解决方案三: 向高手请教一个delphi 数据操作的问题!!! 解决方案四: ios 持久化存储一般有以下几种方法: sqllite core date 持久化缓存 前两个是数据库 第三个是文件存储 解决方案五: 系统的:SQLite