原文发布于:wenghengcong.com
NSCache
NSCache
是系统提供的缓存类,用法类似于NSMutableDictionary
,它与集合的不同如下:
-
NSCache具有自动移除对象的功能,以减少系统占用的内存;
-
NSCache是线程安全的;
-
键对象不会像 NSMutableDictionary 中那样被复制。(键不需要实现 NSCopying 协议)。
NSCache
胜过NSMutableDictionary
之处在于,当系统资源将要耗尽时,它可以自动删减缓存。如果采用普通字典对象,那么就要自己编写挂钩,在系统发出“低内存”通知时,手工删减缓存。而NSCache会自动删减“最久未使用的”(lease recently used
)对象。
NSCache
并不会“拷贝”键,而是“保留”它。
NSCache
是线程安全的,而NSDictionary
不是。对缓存来说,线程安全通常很重要,因为开发者要在某个线程中读数据,此时如果发现缓存中找不到指定的键,那么就要下载该键对应的数据了。而下载完数据之后所要执行的回调函数,有可能会放在背景线程中运行,这样的话,就等于是用另外一个线程来写入缓存了。
NSCache类
下面是该类的头文件:
@interface NSCache <KeyType, ObjectType> : NSObject {
@private
id delegate;
void private[5];
void reserved;
}
@property (copy) NSString name;
@property (nullable, assign) id<NSCacheDelegate> delegate;
-(nullable ObjectType)objectForKey:(KeyType)key;
-(void)setObject:(ObjectType)obj forKey:(KeyType)key; // 0 cost
-(void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)g;
-(void)removeObjectForKey:(KeyType)key;
(void)removeAllObjects;
@property NSUInteger totalCostLimit; // limits are imprecise/not strict
@property NSUInteger countLimit; // limits are imprecise/not strict
@property BOOL evictsObjectsWithDiscardedContent;
@end
@protocol NSCacheDelegate <NSObject>
@optional
-(void)cache:(NSCache )cache willEvictObject:(id)obj;
@end
需要说明的是,关于删减对象的时机,可以通过totalCostLimit
和countLimit
来控制,即“总开销”和“缓存对象总数”。当超过指定的上限后,那么久“可能”删减某对象。之所以,是“可能”,是因为缓存系统并不严格精确,在上限的数值上有小范围的浮动。
在设置缓存对象的下面方法中,还需要关注对象的“开销值”(cost):
-(void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)g;
那么,如何决定这个开销值呢?
只有在很快能计算出“开销值”的情况下,才应该考虑采用这个尺度。否则直接调用:
-(void)setObject:(ObjectType)obj forKey:(KeyType)key; // 0 cost
因为加入计算开销值的过程复杂,就违背了使用缓存的初衷——快速定位对象,比如,要从磁盘中读取文件大小,那么这个开销就过大,不应该计算器开销值。但是加入缓存对象是NSData对象,可以直接获取其数据大小,将其数据大小当做开销值也是可以的。
下面是个实例:
#import "JSNetworkFetcher.h"
@interface JSNetworkFetcher()
{
struct {
unsigned int didReceiveData :1;
unsigned int didFailedWithError :1;
unsigned int didUpdateProgressTo :1;
} _delegateFlags;
NSURL *_url;
NSCache *_cache;
}
@end
@implementation JSNetworkFetcher
/**
* 在设置代理的时候检查方法可达性,并缓存起来
*/
- (void)setDelegate:(id<JSNetworkFetcherDelegate>)delegate
{
_delegate = delegate;
_delegateFlags.didReceiveData = [delegate respondsToSelector:@selector(networkFetcher:didReceiveData:)];
_delegateFlags.didFailedWithError = [delegate respondsToSelector:@selector(networkFetcher:didFailerWithError:)];
_delegateFlags.didUpdateProgressTo = [delegate respondsToSelector:@selector(networkFetcher:didUpdateProgerssTo:)];
}
- (instancetype)init {
if (self == [super init]) {
_cache = [NSCache new];
_cache.countLimit = 100; //限制缓存对象在100个
_cache.totalCostLimit = 5*1024*1024; //限制所有缓存对象大小不超过5MB
}
return self;
}
- (instancetype)initWithUrl:(NSURL*)url {
self = [self init];
_url = url;
return self;
}
- (void)downloadDataFromUrl:(NSURL *)url {
NSData *cacheData = [_cache objectForKey:url];
if (cacheData) {
//命中缓存
[self userData:cacheData];
} else {
JSNetworkFetcher *fetcher = [[JSNetworkFetcher alloc]initWithUrl:url];
[fetcher startWithCompletionHandle:^(NSData *data) {
[_cache setObject:data forKey:url cost:data.length];
[self userData:data];
}];
}
}
//使用数据
- (void)userData:(NSData *)data {
}
- (void)startWithCompletionHandle:( void (^)(NSData* data) )completion {
}
@end
NSCache
类声明中还有一个evictsObjectsWithDiscardedContent
的属性。该属性为了搭配NSPurgeableData
对象而添加的。
NSPurgeableData
是NSMutableData
的子类,实现了NSDiscardableContent
协议。该协议声明如下:
@protocol NSDiscardableContent
@required
- (BOOL)beginContentAccess;
- (void)endContentAccess;
- (void)discardContentIfPossible;
- (BOOL)isContentDiscarded;
@end
如果某个对象实现了该协议所定义的接口,该对象所占的内存就可以根据需要随时丢弃。而NSPurgeableData
实现了该协议就意味着,当系统资源紧张时,可以把保存NSPurgeableData对象那块内存释放掉。
如果需要访问NSPurgeableData
对象,调用其beginContentAccess
方法,告诉它现在还不应该丢弃自己所占有的内存。用完之后,调用endContentAccess
方法,告诉它在必要时可以丢弃自己所占据的内存。这就像retain
和release
类似来管理,只有当NSPurgeableData中purge引用计数
为0时才可以丢弃。purge引用计数
,会在创建时加1,非常像真正的引用计数管理规则。
isContentDiscarded是查询对象内存是否被释放。
那么,NSPurgeableData
对象假如加入到NSCache
中,那么当该对象被系统丢弃时,也会自动从缓存中移除。而上面提到的NSCache中的evictsObjectsWithDiscardedContent
属性,就是是否要开启这个功能的开关。
- (void)downloadDataFromUrl:(NSURL *)url {
NSPurgeableData *cacheData = [_cache objectForKey:url];
if (cacheData) {
//开始使用
[cacheData beginContentAccess]; //purge count +1
//cacheData处理
[self userData:cacheData];
//使用完毕
[cacheData endContentAccess]; //purge count -1
} else {
JSNetworkFetcher *fetcher = [[JSNetworkFetcher alloc]initWithUrl:url];
[fetcher startWithCompletionHandle:^(NSData *data) {
NSPurgeableData *purgeData = [NSPurgeableData dataWithData:data]; //purge count = 1
[_cache setObject:purgeData forKey:url cost:purgeData.length];
//cacheData处理,此处不需要beginContentAccess,因为创建会+1
[self userData:data];
//使用完毕
[purgeData endContentAccess];
}];
}
}
经验总结
-
实现自动缓存是应选用NSCache而非NSDictionary对象,因为NSCache可以提供优雅的自动删减功能,而且是“线程安全的”,此外,它与字典不同,并不会拷贝键;
-
可以给NSCache设置上限,用以限制缓存中的对象总个数及“总成本”,而这些尺度定义了缓存删减其中对象的时机,但是绝对不要把这些尺度当初可靠的“硬限制”。它们仅仅对NSCache起指导作用;
-
将NSPureableData与NSCache搭配使用,可实现自动清除数据的功能,也就是说,当NSPureableData对象所占内存为系统所丢弃是,该对象那自身也会从缓存中移除;
-
如果缓存使用得当,那么应用程序的响应就能提升。只有那种“重新计算起来很费事”的数据,才值得放入缓存,比如那些需要从网络获取或从磁盘读取的数据;
实例
- SDWebImage
- AFNetWorking
...
网友评论