iOS NSCache缓存类的详解

来源:互联网 发布:mac上如何下载office 编辑:程序博客网 时间:2024/06/02 09:40

NSCache:专门做缓存的类

NSCache简介:NSCache是苹果官方提供的缓存类,用法与NSMutableDictionary的用法很相似,在AFNetworking和SDWebImage中,使用它来管理缓存。

NSCache在系统内存很低时,会自动释放一些对象(出自苹果官方文档,不过在模拟器中模拟内存警告时,不会做缓存的清理动作) 为了确保接收到内存警告时能够真正释放内存,最好调用一下removeAllObjects方法。

NScache是线程安全的,在多线程操作中,不需要对Cache加锁。

NScache的key只是做强引用,不需要实现NScopying协议。

NSCache的属性:

 delegate代理属性

totalCostLimit :缓存空间的最大成本,超出上限会自动回收对象。默认值是0没有限制。

countLimit:能够缓存对象的最大数量,默认值也是0(默认没有限制)。

(当超出缓存最大成本或数量时,NSCache会把前面的数据即最开始存的给清除掉)

evictsObjectsWithDiscardedContent:标示是否回收废弃的内容,默认值是YES(自动回收)。

NSCache的方法:

-objectForKey:返回与键值关联的对象。

-setObject: forKey: 在缓存中设置指定键名对应的值。与可变字典不同的是,缓存对象不会对键名做copy操作 0成本

-setObject: forKey: cost: 在缓存中设置指定键名对应的值,并且指定该键值对的成本。成本cost用于计算记录在缓冲中所有对象的总成本。当出现内存警告,或者超出缓存的成本上限时,缓存会开启一个回收过程,删除部分元素。

-removeObjectForKey:删除缓存中指定键名的对象。

-removeAllObjects:删除缓存中的所有对象。

委托方法:

-cache: willEvictObject: 缓存将要删除对象时调用,不能在此方法中修改缓存。仅仅用于后台的打印,以便于程序员的测试。

举例验证如下:

[objc] view plain copy
  1. //  
  2. //  ViewController.m  
  3. //  NScache的演练  
  4. //  
  5. //  Created by apple on 15/10/24.  
  6. //  Copyright (c) 2015年 LiuXun. All rights reserved.  
  7. //  
  8.   
  9. #import "ViewController.h"  
  10.   
  11. @interface ViewController ()<NSCacheDelegate>  
  12.   
  13. // 缓存的容器  
  14. @property (nonatomicstrongNSCache *myCache;  
  15. @end  
  16.   
  17. @implementation ViewController  
  18. -(NSCache *)myCache  
  19. {  
  20.     if (_myCache == nil) {  
  21.         _myCache = [[NSCache alloc] init];  
  22.           
  23.         /**  NSCache类以下几个属性用于限制成本 
  24.          NSUInteger totalCostLimit  "成本" 限制,默认是0(没有限制) 
  25.          NSUInteger countLimit  数量的限制  默认是0 (没有限制) 
  26.           
  27.          // 设置缓存的对象,同时指定限制成本 
  28.          -(void)setObject:(id) obj  forKey:(id) key cost:(NSUInteger) g 
  29.          */  
  30.           
  31.         // 设置数量限额。一旦超出限额,会自动删除之前添加的东西  
  32.         _myCache.countLimit = 30;  // 设置了存放对象的最大数量  
  33.         _myCache.delegate = self;  
  34.     }  
  35.     return _myCache;  
  36. }  
  37.   
  38. -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event  
  39. {  
  40.     for (int i =0 ; i< 100; i++) {  
  41.         // 向缓存中添加对象  
  42.         NSString *str = [NSString stringWithFormat:@"hello - %d", i];  
  43.         [self.myCache setObject:str forKey:@(i)]; // @(i) 相当于  [NSNumber numberWith......]  
  44.     }  
  45.     for (int i=0 ; i< 100; i++) {  
  46.         NSLog(@"%@", [self.myCache objectForKey:@(i)]);  
  47.     }  
  48. }  
  49.   
  50.   
  51. // NSCache的代理方法只有一个  
  52. //  告诉即将要被删除的对象  
  53. -(void)cache:(NSCache *)cache willEvictObject:(id)obj  
  54. {  
  55.     // 此代理方法主要用于程序员的测试  
  56.     NSLog(@"要删除的对象obj-------------%@", obj);  
  57. }  
  58. @end  
没有设置代理时的运行结果如下:



因为限额最大数量是30,所以NSCache把前面的0~69的对象全部删除清理了。

遵循代理方法后,打印结果如下:


发现此代理方法就是在清理缓存对象之前告知程序员,便于调试而已。


 所以在原来的工程中做缓存池的可变字典都可以用NScache进行替代了。代码如下:

CZApp.h

[objc] view plain copy
  1. //  
  2. //  CZApp.h  
  3. //  NSoperation之网络图片下载  
  4. //  
  5. //  Created by apple on 15/10/23.  
  6. //  Copyright (c) 2015年 LiuXun. All rights reserved.  
  7. //  
  8.   
  9. #import <UIKit/UIKit.h>  
  10.   
  11. @interface CZApp : NSObject  
  12. @property(nonatomiccopyNSString *name;  
  13. @property (nonatomiccopyNSString *icon;  
  14. @property (nonatomicstrongNSString *download;  
  15.   
  16. /** 
  17.  保存网络下载的图像 
  18.  */  
  19. // @property(nonatomic, strong) UIImage *image;  
  20.   
  21. +(instancetype) appWithDict:(NSDictionary *) dict;  
  22. @end  
CZApp.m

[objc] view plain copy
  1. //  
  2. //  CZApp.m  
  3. //  NSoperation之网络图片下载  
  4. //  
  5. //  Created by apple on 15/10/23.  
  6. //  Copyright (c) 2015年 LiuXun. All rights reserved.  
  7. //  
  8.   
  9. #import "CZApp.h"  
  10.   
  11. @implementation CZApp  
  12. +(instancetype) appWithDict:(NSDictionary *) dict  
  13. {  
  14.     CZApp *app = [[self alloc] init];  
  15.     [app setValuesForKeysWithDictionary:dict];  
  16.     return app;  
  17. }  
  18. @end  
viewController.m

[objc] view plain copy
  1. //  
  2. //  ViewController.m  
  3. //  NSoperation之网络图片下载  
  4. //  
  5. //  Created by apple on 15/10/23.  
  6. //  Copyright (c) 2015年 LiuXun. All rights reserved.  
  7. //  
  8.   
  9. #import "ViewController.h"  
  10. #import "CZApp.h"  
  11. @interface ViewController ()  
  12. // plist文件数据的容器  
  13. @property (nonatomicstrongNSArray *appList;  
  14.   
  15. // 管理下载的全局队列  
  16. @property (nonatomicstrongNSOperationQueue *opQueue;  
  17.   
  18. /**所有下载的缓冲池*/  
  19. @property (nonatomicstrongNSMutableDictionary *operationCache;  
  20.   
  21. /**保存所有图像的缓存*/  
  22. @property (nonatomicstrongNSCache *imageCache;  
  23. @end  
  24.   
  25.   
  26. @implementation ViewController  
  27.   
  28. // 懒加载  
  29. -(NSArray *)appList  
  30. {  
  31.     if (_appList == nil) {  
  32.         NSArray *dicArray = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil]];  
  33.         // 字典转模型  
  34.         NSMutableArray *arryM = [NSMutableArray array];  
  35.         for(NSDictionary *dict in dicArray){  
  36.             CZApp *app = [CZApp appWithDict:dict];  
  37.             [arryM addObject:app];  
  38.         }  
  39.         _appList = arryM;  
  40.     }  
  41.     return _appList;  
  42. }  
  43.   
  44. -(NSOperationQueue *)opQueue  
  45. {  
  46.     if (_opQueue == nil) {  
  47.         _opQueue = [[NSOperationQueue alloc] init];  
  48.     }  
  49.     return _opQueue;  
  50. }  
  51.   
  52. -(NSMutableDictionary *)operationCache  
  53. {  
  54.     if (_operationCache == nil) {  
  55.         _operationCache = [[NSMutableDictionary alloc] init];  
  56.     }  
  57.     return _operationCache;  
  58. }  
  59.   
  60. -(NSCache *)imageCache  
  61. {  
  62.     if (_imageCache == nil) {  
  63.         _imageCache = [[NSCache alloc] init];  
  64.     }  
  65.     return _imageCache;  
  66. }  
  67.   
  68. #pragma mark - 实现数据源方法  
  69. -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section  
  70. {  
  71.     return self.appList.count;  
  72. }  
  73.   
  74. /** 
  75.  问题1:如果网速比较慢,会很卡 
  76.  解决方法:使用异步下载 
  77.   
  78.  问题2:图片没有Frame,所有cell初始化的时候,给imageView的frame是0。异步下载完之后不显示  解决办法:使用占位图(如果展位图比较大, 自定义cell可以解决) 
  79.   
  80.  问题3:如果图片下载速度不一致,同时用户快速滚动的时候,会因为Cell的重用导致图片混乱 
  81.  解决方法:MVC,使用Model(模型)保存下载的图像,再次刷新表格。 
  82.   
  83.  问题4:在用户快速滚动的时候,会重复添加下载任务到下载队列。 
  84.  解决方法:建立下载操作的缓冲池。首先检查缓冲池里是否有当前图片的下载操作。有的话就不创建下载操作。从而保证一张图片只添加一个下载操作。其实就是建立一个全局的字典属性。 
  85.   
  86.  问题5: 将图片保存到模型里的优缺点 
  87.  优点:不用重复下载,利用MVC刷新表格,不会造成数据混乱,加载速度比较快 
  88.  缺点:内存,所有下载好的图像都会记录在模型里。如果数据比较多(2000)个就会造成内存警告。 
  89.   
  90.  -***图像根模型耦合性太强。导致清理内存非常困难 
  91.  解决办法:模型跟图像分开。在控制器里做缓存。 
  92.  问题6:下载操作缓冲池会越来越大。需要及时清理。 
  93.   
  94.  */  
  95. /** 
  96.  代码重构:1.如果代码太长。 
  97.  目的: 
  98.  - 写的时候,如果思路清楚,能够一次性写完,但是也要注意同构。 
  99.  - 时间长了,不好阅读 
  100.  - 重构代码,便于维护 
  101.   
  102.  重构方法: 
  103.  如果有一部分代码专门解决某一问题,就封装起来。 
  104.  1. 新建一个方法—> 剪切代码。 
  105.  2. 传参数。 
  106.  3. 在原来剪切代码的地方,调用抽取的方法。 
  107.  4. 注意,测试。 
  108.  5. 注意if嵌套,在实际的开发,非常忌讳很深的嵌套。 
  109.  */  
  110.   
  111. -(void)viewDidLoad  
  112. {  
  113.     NSLog(@"%@", [self cachePathWithUrl:@""]);  
  114. }  
  115.   
  116. // cell里面的imageView子控件是懒加载  
  117. -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath  
  118. {  
  119.     static NSString *ID = @"AppCell";  
  120.     UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:ID];  
  121.     // 给Cell设置数据  
  122.     CZApp *app = self.appList[indexPath.row];  
  123.     cell.textLabel.text = app.name;  
  124.     cell.detailTextLabel.text = app.download;  
  125.       
  126.     // 判断模型里面是否有图像  
  127.     if ([self.imageCache objectForKey:app.icon]) { // 内存有图片  
  128.         NSLog(@" 图片已经下载......");  
  129.         cell.imageView.image = [self.imageCache objectForKey:app.icon];  
  130.     }else{  // 内存无图片  
  131.         // 显示图片  
  132.         // 如果沙盒里面有图片,直接从沙盒加载  
  133.         UIImage *image = [UIImage imageWithContentsOfFile:[self cachePathWithUrl:app.icon]];  
  134.         if (image) {   //  沙盒有图片  
  135.             NSLog(@"直接从沙盒加载图片");  
  136.               
  137.             // 1. 设置图片缓存到内存,方便下次从内存直接加载  
  138.             [self.imageCache setObject:image forKey:app.icon];  
  139.               
  140.             // 2. 显示图片到cell  
  141.             cell.imageView.image = [self.imageCache objectForKey:app.icon];  
  142.         }else{  // 沙盒没有图片  
  143.               
  144.             // 显示图片—占位图  
  145.             cell.imageView.image = [UIImage imageNamed:@"user_default"];  
  146. #warning 从这里开始剪切的代码  
  147.               
  148.             // 下载图片  
  149.             [self downloadImage:indexPath];  
  150.         }  
  151.     }  
  152.     return cell;  
  153. }  
  154.   
  155. -(void)downloadImage:(NSIndexPath *)indexPath  
  156. {  
  157.     CZApp *app = self.appList[indexPath.row];  
  158.     /** 
  159.      如果下载缓冲池里面有当前图片的下载操作,就不用创建下载操作,没有才创建 
  160.      缓冲池字典中 key:存放当前图片的url,字符串类型。 
  161.      Value:保存下载操作 
  162.      */  
  163.     if (self.operationCache[app.icon]) {  
  164.         NSLog(@"正在玩命下载中......");  
  165.         return;  
  166.     }  
  167.     // 缓冲池没有下载操作  
  168.       
  169.     // 异步下载图片  
  170.     __weak typeof(self) weakSelf = self;  
  171.     NSBlockOperation  *downLoadOp = [NSBlockOperation blockOperationWithBlock:^{  
  172.         // 模拟延时  
  173.         [NSThread sleepForTimeInterval:2];  
  174.         NSLog(@"正在下载中......");  
  175.           
  176.         //  1. 下载图片(二进制数据)  
  177.         NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:app.icon]];  
  178.         UIImage *image = [UIImage imageWithData:data];  
  179.           
  180.         //  2. 将下载的数据保存到沙盒  
  181.         // 字典的赋值不能为nil,赋值为nil会崩溃  
  182.         if (image) {  
  183.             // 先保存到图片缓存  
  184.             [weakSelf.imageCache setObject:image forKey:app.icon];  
  185.               
  186.             // 将图片写入到沙盒  
  187.             [data writeToFile:[self cachePathWithUrl:app.icon] atomically:YES];  
  188.         }  
  189.           
  190.         // 3 将操作从缓冲池删除——将下载好的图片操作从缓冲池中移除  
  191.         [weakSelf.operationCache removeObjectForKey:app.icon];  
  192.           
  193.         //  4. 在主线程更新UI  
  194.         [[NSOperationQueue mainQueue] addOperationWithBlock:^{  
  195.             [weakSelf.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];  
  196.             /** reload 会重新调用cell的初始化方法, 会重新判断模型里面是否有图像 
  197.              有的话就直接显示 
  198.              */  
  199.         }];  
  200.     }];  
  201.       
  202.     // 将操作添加到队列  
  203.     [weakSelf.opQueue addOperation:downLoadOp];  
  204.     NSLog(@"操作的数量------------->%ld"self.opQueue.operationCount);  
  205.       
  206.     // 将操作添加到缓冲池中(使用图片的url作为key)  
  207.     [weakSelf.operationCache setObject:downLoadOp forKey:app.icon];  
  208. }  
  209.   
  210. -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath  
  211. {   // 点击后查看操作缓冲池内的操作详情  
  212.     NSLog(@"%@"self.operationCache);  
  213. }  
  214.   
  215. /** 
  216.  在真实开发中,一定要注意这个方法 
  217.  */  
  218. -(void)didReceiveMemoryWarning  
  219. {  
  220.     [super didReceiveMemoryWarning];  
  221.       
  222.     // 需要在这里做一些内存清理的工作,如果不处理会被系统强制闪退  
  223.     // 清理内存  
  224.     [self.imageCache  removeAllObjects];  
  225.       
  226.     // 清理操作的缓存  
  227.     [self.operationCache removeAllObjects];  
  228.       
  229.     // 取消下载队列内的任务  
  230.     [self.opQueue cancelAllOperations];  
  231. }  
  232.   
  233. /** 
  234.  拼接一个文件在沙盒的全路径 
  235.  */  
  236. -(NSString *)cachePathWithUrl:(NSString *)urlStr  
  237. {  // 将图片网址名作为作为最后一项  
  238.     // 1. 获得缓存的路径  
  239.     NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];  
  240.       
  241.     // 2. 把路径根urlStr拼接起来  
  242.     return [cachePath stringByAppendingPathComponent:urlStr.lastPathComponent];  
  243. }  
  244. -(void)dealloc  
  245. {  
  246.     NSLog(@"销毁控制器-------------");  
  247. }  
  248. @end  
0 0
原创粉丝点击