iOS 缓存机制

一、应该用哪种缓存技术

在众多可以本地保存数据的技术中,有三种脱颖而出:URL缓存数据模型缓存(利用NSKeyedArchiver)和Core Data

假设你正在开发一个应用,需要缓存数据以改善应用表现出的性能,你应该实现按需缓存(使用数据模型缓存或URL缓存)。

另一方面,如果需要数据能够离线访问,而且具有合理的存储方式以便离线编辑,那么就用高级序列化技术(如Core Data)。

1.URL缓存

内存缓存我们可以使用sdk中的NSURLCache类。NSURLRequest需要一个缓存参数来说明它请求的url何如缓存数据的,我们先看下它的CachePolicy类型。

 

 1NSURLRequestUseProtocolCachePolicyNSURLRequest默认的cachepolicy,使用Protocol协议定义。

 

 2NSURLRequestReloadIgnoringCacheData忽略缓存直接从原始地址下载。

 

 3NSURLRequestReturnCacheDataElseLoad只有在cache中不存在data时才从原始地址下载。

 

 4NSURLRequestReturnCacheDataDontLoad只使用cache数据,如果不存在cache,请求失败;用于没有建立网络连接离线模式;

 

 5NSURLRequestReloadIgnoringLocalAndRemoteCacheData:忽略本地和远程的缓存数据,直接从原始地址下载,与NSURLRequestReloadIgnoringCacheData类似。

 

 6NSURLRequestReloadRevalidatingCacheData:验证本地数据与远程数据是否相同,如果不同则下载远程数据,否则使用本地数据。

 

 NSURLCache还提供了很多方法,来方便我们实现应用程序的缓存机制。下面我通过一个例子来说明,这个例子减少我们对同一个url多次请求。看下面代码:

- (void)viewDidLoad
{
   [superviewDidLoad];
   NSString *paramURLAsString=@"http://blog.sina.com.cn/u/2526279194";
   self.urlCache =[NSURLCachesharedURLCache];
  
   [self.urlCache setMemoryCapacity:1*1024*1024];
   //创建一个nsurl
   self.url = [NSURL URLWithString:paramURLAsString];
   //创建一个请求
   self.request=[NSMutableURLRequest requestWithURL:self.url
                                          cachePolicy:NSURLRequestUseProtocolCachePolicy
                             timeoutInterval:30.0f];
   [self.myWebView loadRequest:self.request];
}

 
这个例子中,我们请求url为http://blog.sina.com.cn/u/2526279194的网站。如果这个url被缓存了,我们直接从缓存中获取数据,否则从http://blog.sina.com.cn/u/2526279194站点上重新获取数据。我们设置了缓存大小为1M。


- (IBAction)reloadWebView:(UIButton *)sender {

   //从请求中获取缓存输出
   NSCachedURLResponse*response =[self.urlCachecachedResponseForRequest:self.request];
   //判断是否有缓存
   if (response!= nil){
       NSLog(@"如果有缓存输出,从缓存中获取数据");
       [self.request setCachePolicy:NSURLRequestReturnCacheDataDontLoad];
   }
   [self.myWebView loadRequest:self.request];
   
   self.connection = nil;
  
   NSURLConnection *newConnection =[[NSURLConnection alloc]initWithRequest:self.request
                                                         delegate:self
                                                   startImmediately:YES];
   self.connection = newConnection;
}

使用下面代码,我将请求的过程打印出来
- (void) connection:(NSURLConnection*)connection
 didReceiveResponse:(NSURLResponse *)response{
   NSLog(@"将接收输出");
}
- (NSURLRequest *)connection:(NSURLConnection *)connection
           willSendRequest:(NSURLRequest *)request
         redirectResponse:(NSURLResponse*)redirectResponse{
   NSLog(@"即将发送请求");
   return(request);
}
- (void)connection:(NSURLConnection *)connection
   didReceiveData:(NSData *)data{
   NSLog(@"接受数据");
   NSLog(@"数据长度为= %lu", (unsigned long)[data length]);
}
-(NSCachedURLResponse *)connection:(NSURLConnection *)connection
              willCacheResponse:(NSCachedURLResponse*)cachedResponse{
   NSLog(@"将缓存输出");
   return(cachedResponse);
}
- (void)connectionDidFinishLoading:(NSURLConnection*)connection{
   NSLog(@"请求完成");
}
- (void)connection:(NSURLConnection *)connection
 didFailWithError:(NSError*)error{
   NSLog(@"请求失败");
}

@end

第一次打印结果如下
 
2013-01-31 15:28:29.923NSURLCacheDemo[27848:907] 即将发送请求
2013-01-31 15:28:30.043NSURLCacheDemo[27848:907] 将接收输出
2013-01-31 15:28:30.045NSURLCacheDemo[27848:907] 接受数据
2013-01-31 15:28:30.047NSURLCacheDemo[27848:907] 数据长度为= 30047
2013-01-31 15:28:30.095NSURLCacheDemo[27848:907] 接受数据
2013-01-31 15:28:30.098NSURLCacheDemo[27848:907] 数据长度为= 3575
2013-01-31 15:28:30.102NSURLCacheDemo[27848:907] 接受数据
2013-01-31 15:28:30.104NSURLCacheDemo[27848:907] 数据长度为= 1482
2013-01-31 15:28:30.105NSURLCacheDemo[27848:907] 将缓存输出
2013-01-31 15:28:30.107NSURLCacheDemo[27848:907] 请求完成
第二次点击打印结果如下
2013-01-31 15:28:31.599NSURLCacheDemo[27848:907] 如果有缓存输出,从缓存中获取数据
2013-01-31 15:28:31.607NSURLCacheDemo[27848:907] 即将发送请求
2013-01-31 15:28:31.840NSURLCacheDemo[27848:907] 将接收输出
2013-01-31 15:28:31.843NSURLCacheDemo[27848:907] 接受数据
2013-01-31 15:28:31.845NSURLCacheDemo[27848:907] 数据长度为= 35104
2013-01-31 15:28:31.846NSURLCacheDemo[27848:907] 请求完成
我们看到没有“将缓存输出”一项,请求到的数据是第一次请求的累积,也就是第二次是从内存中获取数据的。

2.数据模型缓存

-》》数据存在闪存中

在viewWillAppear方法中,查看缓存中是否有显示这个视图所需的数据。如果有就获取数据,再用缓存数据更新用户界面。然后检查缓存中的数据是否已经过期。你的业务规则应该能够确定什么是新数据、什么是旧数据。如果内容是旧的,把数据显示在UI上,同时在后台从服务器获取数据并再次更新UI。如果缓存中没有数据,显示一个转动的圆圈表示正在加载,同时从服务器获取数据。得到数据后,更新UI。

我们需要在viewWillAppear:中实现实际的缓存逻辑。把下面的代码加入viewWillAppear:就可以实现。视图控制器的viewWillAppear:方法中从缓存恢复数据模型对象的代码片段

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,

        NSUserDomainMask, YES);

    NSString *cachesDirectory = [paths objectAtIndex:0];

    NSString *archivePath = [cachesDirectory

        stringByAppendingPathComponent:@"AppCache/MenuItems.archive"];

    NSMutableArray *cachedItems = [NSKeyedUnarchiver

        unarchiveObjectWithFile:archivePath];

if(cachedItems == nil)

      self.menuItems = [AppDelegate.engine localMenuItems];

else

      self.menuItems = cachedItems;

    NSTimeInterval stalenessLevel = [[[[NSFileManager defaultManager]

        attributesOfItemAtPath:archivePath error:nil]

    fileModificationDate] timeIntervalSinceNow];

if(stalenessLevel > THRESHOLD)

      self.menuItems = [AppDelegate.engine localMenuItems];

    [self updateUI];

缓存机制的逻辑流如下所示。

  1. 视图控制器在归档文件MenuItems.archive中检查之前缓存的项并反归档。
  2. 如果MenuItems.archive不存在,视图控制器调用方法从服务器获取数据。
  3. 如果MenuItems.archive存在,视图控制器检查归档文件的修改时间以确认缓存数据有多旧。如果数据过期了(由业务需求决定),再从服务器获取一次数据。否则显示缓存的数据。

接下来,把下面的代码加入viewDidDisappear方法可以把模型(以NSKeyedArchiver的形式)保存在Library/Caches目录中。

视图控制器的viewWillDisappear:方法中缓存数据模型的代码片段

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,

      NSUserDomainMask, YES);

    NSString *cachesDirectory = [paths objectAtIndex:0];

    NSString *archivePath = [cachesDirectory stringByAppendingPathComponent:@"    AppCache/MenuItems.archive"];

    [NSKeyedArchiver archiveRootObject:self.menuItems toFile:archivePath];

重构

当开发者有多个视图控制器时,前面的代码可能会有冗余。我们可以通过抽象出公共代码并移入名为AppCache的新类来避免冗余。AppCache是处理缓存的应用的核心。把公共代码抽象出来放入AppCache可以避免viewWillAppear:和viewWillDisappear:中出现冗余代码。

重构这部分代码,使得视图控制器的viewWillAppear/viewWillDisappear代码块看起来如下所示。

-(void) viewWillAppear:(BOOL)animated {

      self.menuItems = [AppCache getCachedMenuItems];

      [self.tableView reloadData];

if([AppCache isMenuItemsStale] || !self.menuItems) {

        [AppDelegate.engine fetchMenuItemsOnSucceeded:^(NSMutableArray

        *listOfModelBaseObjects) {

        self.menuItems = listOfModelBaseObjects;

        [self.tableView reloadData];

      } onError:^(NSError *engineError) {

        [UIAlertView showWithError:engineError];

      }];

    }

      [super viewWillAppear:animated];

    }

    -(void) viewWillDisappear:(BOOL)animated {

      [AppCache cacheMenuItems:self.menuItems];

      [super viewWillDisappear:animated];

    }

-》》数据存在RAM中

目前为止,所有iOS设备都带有闪存,而闪存有点小问题:它的读写寿命是有限的。尽管这个寿命跟设备的使用寿命比起来很长,但是仍然需要避免过于频繁地读写闪存。在上一个例子中,视图隐藏时是直接缓存到磁盘的,而视图显示时又是直接从磁盘读取的。这种行为会使用户设备的缓存负担很重。为避免这个问题,我们可以再引入一层缓存,利用设备的RAM而不是闪存(用NSMutableDictionary)。在24.2.1节的“实现数据模型缓存”中,我们介绍了创建归档的两种方法:一个是保存到文件,另一个是保存为NSData对象。这次会用到第二个方法,我们会得到一个NSData指针,将该指针保存到NSMutableDictionary中,而不是文件系统里的平面文件。引入内存缓存的另一个好处是,在归档和反归档内容时性能会略有提升。听起来很复杂,实际上并不复杂。本节将介绍如何给AppCache类添加一层透明的、位于内存中的缓存。(“透明”是指调用代码,即视图控制器,甚至不知道这层缓存的存在,而且也不需要改动任何代码。)我们还会设计一个LRU(Least Recently Used,最近最少使用)算法来把缓存的数据保存到磁盘。

以下简单列出了要创建内存缓存需要的步骤。这些步骤将会在下面几节中详细解释。

  1. 添加变量来存放内存缓存数据。
  2. 限制内存缓存大小,并且把最近最少使用的项写入文件,然后从内存缓存中删除。RAM是有限的,达到使用极限就会触发内存警告。收到警告时不释放内存会使应用崩溃。我们当然不希望发生这种事,所以要为内存缓存设置一个最大阈值。当缓存满了以后再添加任何东西时,最近最少使用的对象应该被保存到文件(闪存中)。
  3. 处理内存警告,并把内存缓存以文件形式写入闪存。
  4. 当应用关闭、退出,或进入后台时,把内存缓存全部以文件形式写入闪存。



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值