NSCache

A common problem encountered when developing a Mac OS X or an iOS application that downloads images from the Internet is deciding what to do about caching them. A good first approach is to use a dictionary to store in memory images that have been downloaded, such that they don’t need to be downloaded again if they are requested later. A naïve developer will simply use an NSDictionary (or rather a mutable one) because that’s a commonly used class. However, an even better class, called NSCache, is also part of the Foundation framework and has been designed exactly for this task.

The benefit of NSCache over an NSDictionary is that as system memory becomes full, the cache is automatically pruned. When using a dictionary, you often end up having to write pruning code yourself by hooking into system notifications for low memory. However, NSCache offers this automatically; because it is part of the Foundation framework, it will be able to hook in deeper to the system than you could yourself. An NSCachewill also prune the least recently used objects first. Writing the code to support this yourself with a dictionary would be quite complex.

Also, an NSCache does not copy keys but rather retains them. This is something that can be controlled on NSDictionary but requires more complex code (see Item 49). A cache usually would rather not copy the keys because often, the key will be an object that does not support copying. Since NSCache doesn’t copy by default, it makes it an easier class to work with in these situations. Also, NSCache is thread safe. This is certainly not true of an NSDictionary, which means that you can poke away at an NSCache from multiple threads at the same time without having to introduce any locks of your own. This is usually useful for a cache because you may want to read from it in one thread, and, if a certain key doesn’t exist, you may download the data for that key. The callbacks for downloading may be in a background thread, so you end up adding to the cache in this other thread.

You can control when a cache will prune its contents. Two user-controllable metrics alongside the system resources are a limit on both the number of objects in the cache and the overall “cost” of the objects. Each object can optionally be given a cost when added to the cache. When the total number of objects exceeds the count limit or the total cost exceeds the cost limit, the cache may evict objects, just as it does when the available system memory becomes tight. However, it is important to note that it may evict rather than it will evict. The order in which objects are evicted is implementation specific. In particular, this means that manipulating the cost metric in order to force eviction in a certain order is a bad idea.

The cost metric should be used only when adding an object to the cache if calculating the cost is very cheap. If calculating it is expensive, you may find that using the cache becomes suboptimal, since you are having to calculate this additional factor each time an object is cached. After all, caches are meant to help with making an application more responsive. For example, having to go to the disk to find the size of a file or to a database to determine the cost would be bad ideas. However, an example of a good cost to use is if NSData objects are added to the cache; in that case, you can use the size of that data as the cost. This is already known to theNSData object, and so calculating it is as simple as reading a property.

The following is an example of using a cache:


//NSCache使用

#import <Foundation/Foundation.h>


// Network fetcher class
typedef   void (^EOCNetworkFetcherCompletionHandler)( NSData  *data);
@interface  EOCNetworkFetcher :  NSObject
- ( id )initWithURL:( NSURL* )url;
- ( void )startWithCompletionHandler:
                 ( EOCNetworkFetcherCompletionHandler )handler;
@end

// Class that uses the network fetcher and caches results
@interface  EOCClass :  NSObject
@end

@implementation  EOCClass {
     NSCache  *_cache;
}

- ( id )init {
     if  (( self  = [ super   init ])) {
        _cache = [ NSCache   new ];

         // Cache a maximum of 100 URLs
        _cache. countLimit  =  100 ;

         /**
          * The size in bytes of data is used as the cost,
          * so this sets a cost limit of 5MB.
          */
        _cache. totalCostLimit  =  5  *  1024  *  1024 ;
    }
     return   self ;
}

- ( void )downloadDataForURL:( NSURL* )url {
     NSData  *cachedData = [_cache  objectForKey :url];
     if  (cachedData) {
         // Cache hit
        [ self   useData :cachedData];
    } else {
         // Cache miss
         EOCNetworkFetcher  *fetcher =
            [[ EOCNetworkFetcher  alloc] initWithURL:url];
        [fetcher startWithCompletionHandler:^( NSData  *data){
            [_cache  setObject :data  forKey :url  cost :data. length ];
            [ self   useData :data];
        }];
    }
}

@end


//NSCache 与 NSPurgeableData结合使用

Another class that can be used effectively alongside NSCache is called NSPurgeableData, an NSMutableData subclass that implements a protocol called NSDiscardableContent. This protocol defines an interface for objects whose memory can be discarded, if required. This means that the memory backing NSPurgeableData is freed when system resources are getting low. The method called isContentDiscarded, part of theNSDiscardableContent protocol, returns whether the memory has been freed.

If a purgeable data object needs to be accessed, you callbeginContentAccess to tell it that it should not be discarded now. When you are done with it, you call endContentAccess to tell it that it is free to be discarded, if desired. These calls can be nested, so you can think of them as just like a reference count being incremented and decremented. Only when the reference count is zero can the object be discarded.

If NSPurgeableData objects are added to an NSCache, a purgeable data object that is purged is automatically removed from the cache. This can optionally be turned on or off through the cache’sevictsObjectsWithDiscardedContent property.

The preceding example could therefore be changed to make use of purgeable data, like so:

- (void)downloadDataForURL:(NSURL*)url {
    NSPurgeableData *cachedData = [_cache objectForKey:url];
    if (cachedData) {
        // Stop the data being purged
        [cacheData beginContentAccess];

        // Use the cached data
        [self useData:cachedData];

        // Mark that the data may be purged again
        [cacheData endContentAccess];
    } else {
        // Cache miss
        EOCNetworkFetcher *fetcher =
            [[EOCNetworkFetcher allocinitWithURL:url];
        [fetcher startWithCompletionHandler:^(NSData *data){
            NSPurgeableData *purgeableData =
                [NSPurgeableData dataWithData:data];
            [_cache setObject:purgeableData
                       forKey:url
                         cost:purgeableData.length];

            // Don't need to beginContentAccess as it begins
            // with access already marked

            // Use the retrieved data
            [self useData:data];

            // Mark that the data may be purged now
            [purgeableData endContentAccess];
        }];
    }
}

Note that when a purgeable data object is created, it is returned with a +1 purge reference count, so you do not need to specifically callbeginContentAccess on it, but you must balance the +1 with a call toendContentAccess.


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值