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

2 篇文章 0 订阅

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

  • NSURLCacheNSURLConnection'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 NSURLRequestobject).

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 UIImage objects 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 NSURLResponse objects 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 NSURLConnectionobjects 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 NSURLRequestobject. 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-Controlheader 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 theNSURLCachedResponse, 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 withAFNetworking experience to add additional information. I hope that you have found this helpful.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
缓存策略 typedef NS_ENUM(NSUInteger, YBCacheType){ YBCacheTypeReturnCacheDataThenLoad = 0,///< 有缓存就先返回缓存,同步请求数据 YBCacheTypeReloadIgnoringLocalCacheData, ///< 忽略缓存,重新请求 YBCacheTypeReturnCacheDataElseLoad,///< 有缓存就用缓存,没有缓存就重新请求(用于数据不变时) YBCacheTypeReturnCacheDataDontLoad,///< 有缓存就用缓存,没有缓存就不发请求,当做请求出错处理(用于离线模式) YBCacheTypeReturnCacheDataExpireThenLoad///< 有缓存就用缓存,如果过期了就重新请求 没过期就不请求 }; //get请求 - (void)test_get { NSString *url = @"http://www.baidu.com"; NSDictionary *params = @{@"i":@"1"}; [YBHttpTool get:url params:params success:^(NSDictionary *obj) { //成功 } failure:^(NSError *error) { //失败 }]; } //get带缓存的请求 - (void)test_get_cache { NSString *url = @"http://www.baidu.com"; NSDictionary *params = @{@"i":@"1"}; [YBHttpTool get:url params:params cacheType:YBCacheTypeReturnCacheDataThenLoad success:^(NSDictionary *obj) { //成功 } failure:^(NSError *error) { //失败 }]; } //post请求带缓存 - (void)test_post_cache { NSString *url = @"http://www.baidu.com"; NSDictionary *params = @{@"i":@"1"}; [YBHttpTool post:url params:params cacheType:YBCacheTypeReturnCacheDataThenLoad success:^(NSDictionary *obj) { //成功 } failure:^(NSError *error) { //失败 }]; } //上传单张图片 - (void)test_upload_image { UIImage *image = [UIImage imageNamed:@"1"]; NSData *data = UIImageJPEGRepresentation(image, 0.5); [YBHttpTool uploadImageWithImage:data success:^(NSDictionary *obj) { //上传成功 } failure:^(NSError *error) { //上传失败 }]; }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值