iOS缓存机制详解

应用离线工的两种方式:第一种是**按需缓存**,这种情况下应用缓存起请求应答,就和Web浏览器的工作原理一样;第二种是**预缓存**,这种情况是缓存全部内容(或者最近n条记录)以便离线访问。

IOS缓存机制详解 借鉴网址

http://www.cnblogs.com/qiqibo/p/3520635.html

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

数据模型缓存与URL缓存

按需缓存可以用数据模型缓存或URL缓存来实现。两种方式各有优缺点,要使用哪一种取决于服务器的实现。URL缓存的实现原理和浏览器缓存或代理服务器缓存类似。当服务器设计得体,遵循HTTP 1.1的缓存规范时,这种缓存效果最好。如果服务器是SOAP服务器(或者实现类似于RPC服务器或RESTful服务器),就需要用数据模型缓存。如果服务器遵循HTTP 1.1缓存规范,就用URL缓存。数据模型缓存允许客户端(iOS应用)掌控缓存失效的情形,当开发者实现URL缓存时,服务器通过HTTP 1.1的缓存控制头控制缓存失效。尽管有些程序员觉得这种方式违反直觉,而且实现起来也很复杂(尤其是在服务器端),但这可能是实现缓存的好办法。事实上,MKNetworkKit提供了对HTTP 1.1缓存标准的原生支持。

数据模型缓存:

本节我们来给iHotelApp添加用数据模型缓存实现的按需缓存。按需缓存是在视图从视图层次结构中消失时做的(从技术上讲,是在viewWillDisappear:方法中)。支持缓存的视图控制器的基本结构如图24-1所示。AppCache Architecture的完整代码可从本章的下载源代码中找到。后面讲解的内容假设你已经下载了代码并且可以随时使用。


实现了按需缓存的视图控制器的控制流

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

前面的流程图假定显示在UI上的数据是可以归档的模型。在iHotelApp的MenuItem模型中实现NSCoding协议。NSKeyedArchiver需要模型实现这个协议,如下面的代码片段所示。

MenuItem类的encodeWithCoder方法(MenuItem.m)

- (void)encodeWithCoder:(NSCoder *)encoder

{

[encoder encodeObject:self.itemId forKey:@"ItemId"];

[encoder encodeObject:self.image forKey:@"Image"];

[encoder encodeObject:self.name forKey:@"Name"];

[encoder encodeObject:self.spicyLevel forKey:@"SpicyLevel"];

[encoder encodeObject:self.rating forKey:@"Rating"];

[encoder encodeObject:self.itemDescription forKey:@"ItemDescription"];

[encoder encodeObject:self.waitingTime forKey:@"WaitingTime"];

[encoder encodeObject:self.reviewCount forKey:@"ReviewCount"];

}

复制代码

MenuItem类的initWithCoder方法(MenuItem.m)

复制代码

- (id)initWithCoder:(NSCoder *)decoder

{

if ((self = [super init])) {

self.itemId = [decoder decodeObjectForKey:@"ItemId"];

self.image = [decoder decodeObjectForKey:@"Image"];

self.name = [decoder decodeObjectForKey:@"Name"];

self.spicyLevel = [decoder decodeObjectForKey:@"SpicyLevel"];

self.rating = [decoder decodeObjectForKey:@"Rating"];

self.itemDescription = [decoder

decodeObjectForKey:@"ItemDescription"];

self.waitingTime = [decoder decodeObjectForKey:@"WaitingTime"];

self.reviewCount = [decoder decodeObjectForKey:@"ReviewCount"];

}

return self;

}

复制代码

就像之前提到过的,可以用Accessorizer来生成NSCoding协议的实现。

根据图24-1中的缓存流程图,我们需要在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];

复制代码

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

视图控制器在归档文件MenuItems.archive中检查之前缓存的项并反归档。

如果MenuItems.archive不存在,视图控制器调用方法从服务器获取数据。

如果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];

复制代码

视图消失时要把menuItems数组的内容保存在归档文件中。注意,如果不是在viewWillAppear:方法中从服务器获取数据的话,这种情况不能缓存。

所以,只需在视图控制器中加入不到10行的代码(并将Accessorizer生成的几行代码加入模型),就可以为应用添加缓存支持了。

重构

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

重构这部分代码,使得视图控制器的viewWillAppear/viewWillDisappear代码块看起来如下所示。加粗部分显示重构时所做的修改,我会在代码后面解释。

视图控制器的viewWillAppear:方法中用AppCache类缓存数据模型的重构代码片段(MenuItemsViewController.m)

复制代码

-(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];

}

复制代码

AppCache类把判断数据是否过期的逻辑从视图控制器中抽象出来了,还把缓存保存的位置也抽象出来了。稍后在本章中我们还会修改AppCache,再引入一层缓存,内容会保存在内存中。

因为AppCache抽象出了缓存的保存位置,我们就不需要为复制粘贴代码来获得应用的缓存目录而操心了。如果应用类似于iHotelApp,开发者可通过为每个用户创建子目录即可轻松增强缓存数据的安全性。然后我们就可以修改AppCache中的辅助方法,现在它返回的是缓存目录,我们可以让它返回当前登录用户的子目录。这样,一个用户缓存的数据就不会被随后登录的用户看到了。

完整的代码可以从本书网站上本章的源代码下载中获取。

缓存版本控制:

我们在上一节中写的AppCache类从视图控制器中抽象出了按需缓存。当视图出现和消失时,缓存就在幕后工作。然而,当你更新应用时,模型类可能会发生变化,这意味着之前归档的任何数据将不能恢复到新的模型上。正如之前所讲,对按需缓存来说,数据并没有那么重要,开发者可以删除数据并更新应用。我会展示可以用来在版本升级时删除缓存目录的代码片段。

iOS中验证模型:

第二个是验证模型,服务器通常会发送一个校验和(Etag)。后续所有从缓存获得资源的请求都应该用这个校验和向服务器**重新验证**资源是否有变化。如果校验和匹配,服务器就返回一个HTTP 304 Not Modified的状态码。

IOS内存缓存:

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

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

添加变量来存放内存缓存数据。

限制内存缓存大小,并且把最近最少使用的项写入文件,然后从内存缓存中删除。RAM是有限的,达到使用极限就会触发内存警告。收到警告时不释放内存会使应用崩溃。我们当然不希望发生这种事,所以要为内存缓存设置一个最大阈值。当缓存满了以后再添加任何东西时,最近最少使用的对象应该被保存到文件(闪存中)。

处理内存警告,并把内存缓存以文件形式写入闪存。

当应用关闭、退出,或进入后台时,把内存缓存全部以文件形式写入闪存。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • IOS缓存机制详解 资料均来自互联网,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法...
    winsonink阅读 2,356评论 3 14
  • 为什么要有缓存 应用需要离线工作的主要原因就是改善应用所表现出的性能。将应用内容缓存起来就可以支持离线。我们可以用...
    milk_powder阅读 2,841评论 6 24
  • 27、ViewController的didReceiveMemoryWarning是在什么时候调用的?默认的操作是...
    烟雨平生花飞舞阅读 561评论 0 1
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 作者si1ence2016.05.20 10:24* http://www.jianshu.com/p/bc3f8...
    Kiddz阅读 1,173评论 0 12