應(yīng)用場(chǎng)景:
iOS中需要頻繁讀取的數(shù)據(jù)剪芥,都可以用NSCache把數(shù)據(jù)緩存到內(nèi)存中提高讀取性能雇锡。
正文:
一:定義
- NSCache是系統(tǒng)提供的一種類似于集合(NSMutableDictionary)的緩存蔚龙,它與集合的不同如下:
- NSCache具有自動(dòng)刪除的功能蔓榄,以減少系統(tǒng)占用的內(nèi)存;
- NSCache是線程安全的鞠值,不需要加線程鎖墓赴;
- 鍵對(duì)象不會(huì)像 NSMutableDictionary 中那樣被復(fù)制竞膳。(鍵不需要實(shí)現(xiàn) NSCopying 協(xié)議)。
二:屬性介紹
NSCache的屬性以及方法介紹:
@property NSUInteger totalCostLimit;
設(shè)置緩存占用的內(nèi)存大小诫硕,并不是一個(gè)嚴(yán)格的限制坦辟,當(dāng)總數(shù)超過(guò)了totalCostLimit設(shè)定的值,系統(tǒng)會(huì)清除一部分緩存章办,直至總消耗低于totalCostLimit的值锉走。
@property NSUInteger countLimit;
設(shè)置緩存對(duì)象的大小,這也不是一個(gè)嚴(yán)格的限制藕届。
- (id)objectForKey:(id)key;
獲取緩存對(duì)象挪蹭,基于key-value對(duì)
- (void)setObject:(id)obj forKey:(id)key; // 0 cost
存儲(chǔ)緩存對(duì)象,考慮緩存的限制屬性休偶;
- (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g;
存儲(chǔ)緩存對(duì)象梁厉,cost是提前知道該緩存對(duì)象占用的字節(jié)數(shù),也會(huì)考慮緩存的限制屬性椅贱,建議直接使用 - (void)setObject:(id)obj forKey:(id)key;
NSCacheDelegate代理
三:代理屬性聲明如下
@property (assign) id<NSCacheDelegate>delegate;
實(shí)現(xiàn)了NSCacheDelegate代理的對(duì)象懂算,在緩存對(duì)象即將被清理的時(shí)候只冻,系統(tǒng)回調(diào)代理方法如下:
- (void)cache:(NSCache *)cache willEvictObject:(id)obj;
第一個(gè)參數(shù)是當(dāng)前緩存(NSCache)庇麦,不要修改該對(duì)象;
第二個(gè)參數(shù)是當(dāng)前將要被清理的對(duì)象喜德,如果需要存儲(chǔ)該對(duì)象山橄,可以在此操作(存入Sqlite or CoreData);
該代理方法的調(diào)用會(huì)在緩存對(duì)象即將被清理的時(shí)候調(diào)用,如下場(chǎng)景會(huì)調(diào)用:
1. - (void)removeObjectForKey:(id)key; 手動(dòng)刪除對(duì)象舍悯;
2. 緩存對(duì)象超過(guò)了NSCache的屬性限制航棱;(countLimit 和 totalCostLimit )
3. App進(jìn)入后臺(tái)會(huì)調(diào)用;
4. 系統(tǒng)發(fā)出內(nèi)存警告萌衬;
四:NSDiscardableContent協(xié)議
NSDiscardableContent是一個(gè)協(xié)議饮醇,實(shí)現(xiàn)這個(gè)協(xié)議的目的是為了讓我們的對(duì)象在不被使用時(shí),可以將其丟棄秕豫,以讓程序占用更少的內(nèi)存朴艰。
一個(gè)NSDiscardableContent對(duì)象的生命周期依賴于一個(gè)“counter”變量观蓄。一個(gè)NSDiscardableContent對(duì)象實(shí)際是一個(gè)可清理內(nèi)存塊,這個(gè)內(nèi)存記錄了對(duì)象當(dāng)前是否被其它對(duì)象使用祠墅。如果這塊內(nèi)存正在被讀取侮穿,或者仍然被需要,則它的counter變量是大于或等于1的毁嗦;當(dāng)它不再被使用時(shí)亲茅,就可以丟棄,此時(shí)counter變量將等于0狗准。當(dāng)counter變量等于0時(shí)克锣,如果當(dāng)前時(shí)間點(diǎn)內(nèi)存比較緊張的話,內(nèi)存塊就可能被丟棄驶俊。這點(diǎn)類似于MRC&ARC娶耍,對(duì)象內(nèi)存回收機(jī)制。
- (void)discardContentIfPossible
當(dāng)counter等于0的時(shí)候饼酿,為了丟棄這些對(duì)象榕酒,會(huì)調(diào)用這個(gè)方法。
默認(rèn)情況下故俐,NSDiscardableContent對(duì)象的counter變量初始值為1想鹰,以確保對(duì)象不會(huì)被內(nèi)存管理系統(tǒng)立即釋放。
- (BOOL)beginContentAccess (counter++)
調(diào)用該方法药版,對(duì)象的counter會(huì)加1辑舷;
與beginContentAccess相對(duì)應(yīng)的是endContentAccess。如果可丟棄內(nèi)存不再被訪問(wèn)時(shí)調(diào)用槽片。其聲明如下:
- (void)endContentAccess (counter--)
該方法會(huì)減少對(duì)象的counter變量何缓,通常是讓對(duì)象的counter值變回為0,這樣在對(duì)象的內(nèi)容不再被需要時(shí)还栓,就要以將其丟棄碌廓。
NSCache類提供了一個(gè)屬性,來(lái)標(biāo)識(shí)緩存是否自動(dòng)舍棄那些內(nèi)存已經(jīng)被丟棄的對(duì)象(默認(rèn)該屬性為YES)剩盒,其聲明如下:
@property BOOL evictsObjectsWithDiscardedContent
如果設(shè)置為YES谷婆,則在對(duì)象的內(nèi)存被丟棄時(shí)舍棄對(duì)象。
個(gè)人建議:如果需要使用緩存辽聊,直接用系統(tǒng)的NSCache就OK了纪挎,不要做死。
NSCache就寫到這里了跟匆,歡迎大家來(lái)指正錯(cuò)誤异袄,我們一起進(jìn)步,感謝大家的閱讀玛臂。
使用場(chǎng)景
一: 緩存數(shù)量限制代碼示例
代碼演練
需要實(shí)現(xiàn)NSCacheDelegate
@interface ViewController () <NSCacheDelegate>
實(shí)現(xiàn)代理方法:
// MARK: NSCache Delegate
// 當(dāng)緩存中的對(duì)象被清除的時(shí)候烤蜕,會(huì)自動(dòng)調(diào)用
// obj 就是要被清理的對(duì)象
// 提示:不建議平時(shí)開(kāi)發(fā)時(shí)重寫埠帕!僅供調(diào)試使用
- (void)cache:(NSCache *)cache willEvictObject:(id)obj {
[NSThread sleepForTimeInterval:0.5];
NSLog(@"清除了-------> %@", obj);
}
聲明NSCache變量:
@property (nonatomic, strong) NSCache *cache;
懶加載:
- (NSCache *)cache {
if (_cache == nil) {
_cache = [[NSCache alloc] init];
// 設(shè)置數(shù)量限制,最大限制為10
_cache.countLimit = 10;
_cache.delegate = self;
}
return _cache;
}
測(cè)試Demo:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (int i = 0; i < 20; ++i) {
NSString *str = [NSString stringWithFormat:@"hello - %04d", i];
NSLog(@"設(shè)置 %@", str);
// 添加到緩存
[self.cache setObject:str forKey:@(i)];
}
// - 查看緩存內(nèi)容,NSCache 沒(méi)有提供遍歷的方法玖绿,只支持用 key 來(lái)取值
for (int i = 0; i < 20; ++i) {
NSLog(@"緩存中----->%@", [self.cache objectForKey:@(i)]);
}
}
運(yùn)行結(jié)果:
2015-03-25 09:27:19.953 01-NSCache演練[26010:681046] 設(shè)置 hello - 0000
2015-03-25 09:27:19.954 01-NSCache演練[26010:681046] 設(shè)置 hello - 0001
2015-03-25 09:27:19.954 01-NSCache演練[26010:681046] 設(shè)置 hello - 0002
2015-03-25 09:27:19.954 01-NSCache演練[26010:681046] 設(shè)置 hello - 0003
2015-03-25 09:27:19.954 01-NSCache演練[26010:681046] 設(shè)置 hello - 0004
2015-03-25 09:27:19.954 01-NSCache演練[26010:681046] 設(shè)置 hello - 0005
2015-03-25 09:27:19.954 01-NSCache演練[26010:681046] 設(shè)置 hello - 0006
2015-03-25 09:27:19.955 01-NSCache演練[26010:681046] 設(shè)置 hello - 0007
2015-03-25 09:27:19.955 01-NSCache演練[26010:681046] 設(shè)置 hello - 0008
2015-03-25 09:27:19.955 01-NSCache演練[26010:681046] 設(shè)置 hello - 0009
2015-03-25 09:27:19.955 01-NSCache演練[26010:681046] 設(shè)置 hello - 0010
2015-03-25 09:27:20.456 01-NSCache演練[26010:681046] 清除了-------> hello - 0000
2015-03-25 09:27:20.457 01-NSCache演練[26010:681046] 設(shè)置 hello - 0011
2015-03-25 09:27:20.957 01-NSCache演練[26010:681046] 清除了-------> hello - 0001
2015-03-25 09:27:20.957 01-NSCache演練[26010:681046] 設(shè)置 hello - 0012
2015-03-25 09:27:21.458 01-NSCache演練[26010:681046] 清除了-------> hello - 0002
2015-03-25 09:27:21.459 01-NSCache演練[26010:681046] 設(shè)置 hello - 0013
2015-03-25 09:27:21.959 01-NSCache演練[26010:681046] 清除了-------> hello - 0003
2015-03-25 09:27:21.959 01-NSCache演練[26010:681046] 設(shè)置 hello - 0014
2015-03-25 09:27:22.461 01-NSCache演練[26010:681046] 清除了-------> hello - 0004
2015-03-25 09:27:22.461 01-NSCache演練[26010:681046] 設(shè)置 hello - 0015
2015-03-25 09:27:22.962 01-NSCache演練[26010:681046] 清除了-------> hello - 0005
2015-03-25 09:27:22.962 01-NSCache演練[26010:681046] 設(shè)置 hello - 0016
2015-03-25 09:27:23.464 01-NSCache演練[26010:681046] 清除了-------> hello - 0006
2015-03-25 09:27:23.464 01-NSCache演練[26010:681046] 設(shè)置 hello - 0017
2015-03-25 09:27:23.965 01-NSCache演練[26010:681046] 清除了-------> hello - 0007
2015-03-25 09:27:23.965 01-NSCache演練[26010:681046] 設(shè)置 hello - 0018
2015-03-25 09:27:24.466 01-NSCache演練[26010:681046] 清除了-------> hello - 0008
2015-03-25 09:27:24.466 01-NSCache演練[26010:681046] 設(shè)置 hello - 0019
2015-03-25 09:27:24.967 01-NSCache演練[26010:681046] 清除了-------> hello - 0009
2015-03-25 09:27:24.967 01-NSCache演練[26010:681046] 緩存中----->(null)
2015-03-25 09:27:24.967 01-NSCache演練[26010:681046] 緩存中----->(null)
2015-03-25 09:27:24.968 01-NSCache演練[26010:681046] 緩存中----->(null)
2015-03-25 09:27:24.968 01-NSCache演練[26010:681046] 緩存中----->(null)
2015-03-25 09:27:24.968 01-NSCache演練[26010:681046] 緩存中----->(null)
2015-03-25 09:27:24.968 01-NSCache演練[26010:681046] 緩存中----->(null)
2015-03-25 09:27:24.969 01-NSCache演練[26010:681046] 緩存中----->(null)
2015-03-25 09:27:24.969 01-NSCache演練[26010:681046] 緩存中----->(null)
2015-03-25 09:27:24.969 01-NSCache演練[26010:681046] 緩存中----->(null)
2015-03-25 09:27:24.969 01-NSCache演練[26010:681046] 緩存中----->(null)
2015-03-25 09:27:24.969 01-NSCache演練[26010:681046] 緩存中----->hello - 0010
2015-03-25 09:27:24.970 01-NSCache演練[26010:681046] 緩存中----->hello - 0011
2015-03-25 09:27:24.970 01-NSCache演練[26010:681046] 緩存中----->hello - 0012
2015-03-25 09:27:24.970 01-NSCache演練[26010:681046] 緩存中----->hello - 0013
2015-03-25 09:27:24.970 01-NSCache演練[26010:681046] 緩存中----->hello - 0014
2015-03-25 09:27:24.971 01-NSCache演練[26010:681046] 緩存中----->hello - 0015
2015-03-25 09:27:24.971 01-NSCache演練[26010:681046] 緩存中----->hello - 0016
2015-03-25 09:27:24.971 01-NSCache演練[26010:681046] 緩存中----->hello - 0017
2015-03-25 09:27:24.971 01-NSCache演練[26010:681046] 緩存中----->hello - 0018
2015-03-25 09:27:24.971 01-NSCache演練[26010:681046] 緩存中----->hello - 0019
總結(jié)
通過(guò)打印結(jié)果可以知道敛瓷,當(dāng)超多最大成本限制的時(shí)候,會(huì)先清除緩存中的一條數(shù)據(jù)斑匪,再存入一條新的數(shù)據(jù)呐籽。最后緩存中只能保存最大成本數(shù)的數(shù)據(jù),即10條蚀瘸。
二:關(guān)聯(lián)沙箱代碼示例:(減少頻繁讀取文件時(shí)間)
代碼示例
#import "AHPersonInfoManager.h"
#import "YYModel.h"
@interface AHPersonInfoManager()
//用戶信息模型
@property(nonatomic,strong)AHPersonInfoModel *model;
//個(gè)人信息緩存
@property(nonatomic,strong)NSCache *infoModelCache;
@end
@implementation AHPersonInfoManager
+(instancetype)manager{
static AHPersonInfoManager *personInfoManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
@synchronized (personInfoManager) {
personInfoManager = [[AHPersonInfoManager alloc]init];
personInfoManager.infoModelCache = [[NSCache alloc]init];
}
});
return personInfoManager;
}
#pragma mark 個(gè)人資料
-(AHPersonInfoModel*)getInfoModel{
//讀取緩存
if (_infoModelCache) {
return [_infoModelCache objectForKey:@"infoModel"];
}
//讀取文件
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
//獲取完整路徑
NSString *documentsDirectory = [paths objectAtIndex:0];
// LOG(@"homeDirectory = %@", documentsDirectory);
NSString *plistPath = [documentsDirectory stringByAppendingPathComponent:PersonInfoFilePath];
//讀出文件
if ( [[NSFileManager defaultManager] fileExistsAtPath:plistPath]) {
NSMutableDictionary *infoDic = [[[NSMutableDictionary alloc] initWithContentsOfFile:plistPath] mutableCopy];
_model = [AHPersonInfoModel yy_modelWithDictionary:infoDic];
}else{
//防nil
_model = [[AHPersonInfoModel alloc]init];
}
//寫入緩存
[_infoModelCache setObject:_model forKey:@"infoModel"];
return _model;
}
-(void)setInfoModel:(AHPersonInfoModel*)infoModel{
@synchronized (self) {
//寫入緩存
if (!_infoModelCache) {
_infoModelCache = [[NSCache alloc]init];
}
[_infoModelCache setObject:infoModel forKey:@"infoModel"];
//寫入文件
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
//獲取完整路徑
NSString *documentsDirectory = [paths objectAtIndex:0];
// LOG(@"homeDirectory = %@", documentsDirectory);
NSString *plistPath = [documentsDirectory stringByAppendingPathComponent:PersonInfoFilePath];
NSDictionary *dic = [infoModel yy_modelToJSONObject];
//寫入文件
[dic writeToFile:plistPath atomically:YES];
_model = infoModel;
}
}
-(void)setWithJson:(id)json{
@synchronized (self) {
NSMutableDictionary *newInfoDic = [json yy_modelToJSONObject];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
//獲取完整路徑
NSString *documentsDirectory = [paths objectAtIndex:0];
// LOG(@"homeDirectory = %@", documentsDirectory);
NSString *plistPath = [documentsDirectory stringByAppendingPathComponent:PersonInfoFilePath];
//原模型字典
NSMutableDictionary *oldInfoDic= [[[NSMutableDictionary alloc] initWithContentsOfFile:plistPath] mutableCopy];
//防nil
if (!([oldInfoDic allKeys].count >0)) {
oldInfoDic = [NSMutableDictionary dictionary];
}
//修改對(duì)應(yīng)的key-value
NSArray *keyArray = [newInfoDic allKeys];
if (keyArray.count>0) {
for (NSString *key in keyArray) {
if ( [newInfoDic objectForKey:key]) {
//去除星座
if ([key isEqualToString:@"constellation"]) {
}else{
[oldInfoDic setObject:newInfoDic[key] forKey:key];
}
}
}
[oldInfoDic writeToFile:plistPath atomically:YES];
//寫入緩存
if (!_infoModelCache) {
_infoModelCache = [[NSCache alloc]init];
}
AHPersonInfoModel *infoModel = [AHPersonInfoModel yy_modelWithJSON:oldInfoDic];
[_infoModelCache setObject:infoModel forKey:@"infoModel"];
}
}
}