iOS緩存機(jī)制

為什么要有緩存

應(yīng)用需要離線工作的主要原因就是改善應(yīng)用所表現(xiàn)出的性能缔恳。將應(yīng)用內(nèi)容緩存起來就可以支持離線瘪松。我們可以用兩種不同的緩存來使應(yīng)用離線工作。第一種是**按需緩存**,這種情況下應(yīng)用緩存起請求應(yīng)答雪营,就和Web瀏覽器的工作原理一樣小作;第二種是**預(yù)緩存**恒序,這種情況是緩存全部內(nèi)容(或者最近n條記錄)以便離線訪問三圆。

像第14章中開發(fā)的Web服務(wù)應(yīng)用利用按需緩存技術(shù)來改善可感知的性能而不是提供離線訪問。離線訪問只是無心插柳的結(jié)果污呼。Twitter和Foursquare就是很好的例子砂沛。這類應(yīng)用得到的數(shù)據(jù)通常很快就會(huì)過時(shí)。對(duì)于一條幾天前的推文或者朋友上周在哪里你能有多大興趣曙求?一般來說碍庵,一條推文或者一條簽到的信息只在幾個(gè)小時(shí)內(nèi)有意義,而24小時(shí)之后就變得無關(guān)緊要悟狱。不過大部分Twitter客戶端還是會(huì)緩存推文静浴,而Foursquare的官方客戶端在無網(wǎng)絡(luò)連接的情況下打開,會(huì)顯示上次的狀態(tài)挤渐。

大家可以用自己喜歡的Twitter客戶端來試一下苹享,Twitter for iPhone、Tweetbot或其他應(yīng)用:打開某個(gè)朋友的個(gè)人資料并瀏覽他的時(shí)間線浴麻。應(yīng)用會(huì)獲取時(shí)間線并填充頁面得问。加載時(shí)間線時(shí)會(huì)看到一個(gè)表示正在加載的圓圈在旋轉(zhuǎn)。現(xiàn)在進(jìn)入另一個(gè)頁面软免,然后再回來打開時(shí)間線宫纬。你會(huì)發(fā)現(xiàn)這次是瞬間加載的。應(yīng)用還是在后臺(tái)刷新內(nèi)容(在上次打開的基礎(chǔ)上)膏萧,但是它會(huì)顯示上次緩存的內(nèi)容而不是無趣地轉(zhuǎn)圈漓骚,這樣看起來就快多了蝌衔。如果沒有緩存,用戶每次打開一個(gè)頁面都會(huì)看到圓圈在旋轉(zhuǎn)蝌蹂。無論網(wǎng)絡(luò)連接快還是慢噩斟,減小網(wǎng)絡(luò)加載慢的影響,讓它看起來很快孤个,是iOS開發(fā)者的責(zé)任剃允。這就能大大改善用戶滿意度,從而提高了應(yīng)用在App Store中的評(píng)分齐鲤。

另一種緩存更加重視被緩存數(shù)據(jù)斥废,并且能快速編輯被緩存的記錄而無需連接到服務(wù)器。代表應(yīng)用包括Google Reader客戶端佳遂,稍后閱讀類的應(yīng)用Instapaper等营袜。

緩存的策略:

上一節(jié)中討論到按需緩存和預(yù)緩存撒顿,它們在設(shè)計(jì)和實(shí)現(xiàn)上有很大的不同丑罪。按需緩存是指把從服務(wù)器獲取的內(nèi)容以某種格式存放在本地文件系統(tǒng),之后對(duì)于每次請求凤壁,檢查緩存中是否存在這塊數(shù)據(jù)吩屹,只有當(dāng)數(shù)據(jù)不存在(或者過期)的情況下才從服務(wù)器獲取。這樣的話拧抖,緩存層就和處理器的高速緩存差不多煤搜。獲取數(shù)據(jù)的速度比數(shù)據(jù)本身重要。而預(yù)緩存是把內(nèi)容放在本地以備將來訪問唧席。對(duì)預(yù)緩存來說擦盾,數(shù)據(jù)丟失或者緩存不命中是不可接受的,比方用戶下載了文章準(zhǔn)備在地鐵上看淌哟,但卻發(fā)現(xiàn)設(shè)備上不存在這些文章迹卢。

像Twitter、Facebook和Foursquare這樣的應(yīng)用屬于按需緩存徒仓,而Instapaper和Google Reader等客戶端則屬于預(yù)緩存腐碱。

實(shí)現(xiàn)預(yù)緩存可能需要一個(gè)后臺(tái)線程訪問數(shù)據(jù)并以有意義的格式保存,以便本地緩存無需重新連接服務(wù)器即可被編輯掉弛。編輯可能是“標(biāo)記記錄為已讀”或“加入收藏”症见,或其他類似的操作。這里**有意義的格式**是指可以用這種方式保存內(nèi)容殃饿,不用和服務(wù)器通信就可以在本地作出上面提到的修改谋作,并且一旦再次連上網(wǎng)就可以把變更發(fā)送回服務(wù)器。這種能力和Foursquare等應(yīng)用不同乎芳,雖然使用后者你能在無網(wǎng)絡(luò)連接的情況下看到自己是哪些地點(diǎn)的地主(Mayor)瓷们,當(dāng)然前提是進(jìn)行了緩存业栅,但無法成為某個(gè)地點(diǎn)的地主。Core Data(或者任何結(jié)構(gòu)化存儲(chǔ))是實(shí)現(xiàn)這種緩存的一種方式谬晕。

按需緩存工作原理類似于瀏覽器緩存碘裕。它允許我們查看以前查看或者訪問過的內(nèi)容。按需緩存可以通過在打開一個(gè)視圖控制器時(shí)按需地緩存數(shù)據(jù)模型(創(chuàng)建一個(gè)數(shù)據(jù)模型緩存)來實(shí)現(xiàn)攒钳,而不是在一個(gè)后臺(tái)線程上做這件事帮孔。也可以在一個(gè)URL請求返回成功(200 OK)應(yīng)答時(shí)實(shí)現(xiàn)按需緩存(創(chuàng)建一個(gè)URL緩存)。兩種方法各有利弊不撑,稍后我會(huì)在24.3節(jié)和24.6節(jié)中解釋各個(gè)方法的優(yōu)缺點(diǎn)文兢。

選擇使用按需緩存還是預(yù)緩存的一個(gè)簡便方法是判斷是否需要在下載數(shù)據(jù)之后處理數(shù)據(jù)。后期處理數(shù)據(jù)可能是以用戶產(chǎn)生編輯的形式焕檬,也可能是更新下載的數(shù)據(jù)姆坚,比如重寫HTML頁面里的圖片鏈接以指向本地緩存圖片。如果一個(gè)應(yīng)用需要做上面提到的任何后期處理实愚,就必須實(shí)現(xiàn)預(yù)緩存兼呵。

存儲(chǔ)緩存:

第三方應(yīng)用只能把信息保存在應(yīng)用程序的沙盒中。因?yàn)榫彺鏀?shù)據(jù)不是用戶產(chǎn)生的腊敲,所以它應(yīng)該被保存在NSCachesDirectory击喂,而不是NSDocumentsDirectory。為緩存數(shù)據(jù)創(chuàng)建獨(dú)立目錄是一項(xiàng)不錯(cuò)的實(shí)踐碰辅。在下面的例子中懂昂,我們將在Library/caches文件夾下創(chuàng)建名為MyAppCache的目錄∶槐觯可以這樣創(chuàng)建:

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,

NSUserDomainMask, YES);

NSString *cachesDirectory = [paths objectAtIndex:0];

cachesDirectory = [cachesDirectory

stringByAppendingPathComponent:@"MyAppCache"];

把緩存存儲(chǔ)在緩存文件夾下的原因是iCloud(和iTunes)的備份不包括此目錄凌彬。如果在Documents目錄下創(chuàng)建了大尺寸的緩存文件,它們會(huì)在備份的時(shí)候被上傳到iCloud并且很快就用完有限的空間(寫作本書時(shí)大約為5 GB)循衰。你不會(huì)這么干的——誰不想成為用戶iPhone上的良民铲敛?NSCachesDirectory正是解決這個(gè)問題的。

預(yù)緩存是用高級(jí)數(shù)據(jù)庫(比如原始的SQLite)或者對(duì)象序列化框架(比如Core Data)實(shí)現(xiàn)的羹蚣。我們需要根據(jù)需求認(rèn)真選擇不同的技術(shù)原探。本節(jié)第5點(diǎn)“應(yīng)該用哪種緩存技術(shù)”給出了一些建議:什么時(shí)候該用URL緩存或者數(shù)據(jù)模型緩存,而什么時(shí)候又該用Core Data顽素。接下來先看一下數(shù)據(jù)模型緩存的實(shí)現(xiàn)細(xì)節(jié)咽弦。

1. 實(shí)現(xiàn)數(shù)據(jù)模型緩存

可以用NSKeyedArchiver類來實(shí)現(xiàn)數(shù)據(jù)模型緩存。為了把模型對(duì)象用NSKeyedArchiver歸檔胁出,模型類需要遵循NSCoding協(xié)議型型。

NSCoding協(xié)議方法

- (void)encodeWithCoder:(NSCoder *)aCoder;

- (id)initWithCoder:(NSCoder *)aDecoder;

當(dāng)模型遵循NSCoding協(xié)議時(shí),歸檔對(duì)象就很簡單全蝶,只要調(diào)用下列方法中的一個(gè):

[NSKeyedArchiver archiveRootObject:objectForArchiving

toFile:archiveFilePath];

[NSKeyedArchiver archivedDataWithRootObject:objectForArchiving];

第一個(gè)方法在archiveFilePath指定的路徑下創(chuàng)建一個(gè)歸檔文件闹蒜。第二個(gè)方法則返回一個(gè)NSData對(duì)象寺枉。NSData通常更快,因?yàn)闆]有文件訪問開銷绷落,但對(duì)象保存在應(yīng)用的內(nèi)存中姥闪,如果不定期檢查的話會(huì)很快用完內(nèi)存。在iPhone上定期緩存到閃存的功能也是不明智的砌烁,因?yàn)楦脖P不同筐喳,閃存讀寫壽命是有限的。開發(fā)者得盡可能平衡好兩者的關(guān)系函喉。24.3節(jié)會(huì)詳細(xì)介紹歸檔實(shí)現(xiàn)緩存避归。

NSKeyedUnarchiver類用于從文件(或者NSData指針)反歸檔模型。根據(jù)反歸檔的位置管呵,選擇使用下面兩個(gè)類方法梳毙。

[NSKeyedUnarchiver unarchiveObjectWithData:data];

[NSKeyedUnarchiver unarchiveObjectWithFile:archiveFilePath];

這四個(gè)方法在轉(zhuǎn)化序列化數(shù)據(jù)時(shí)能派上用場。

使用任何NSKeyedArchiver/NSKeyedUnarchiver的前提是模型實(shí)現(xiàn)了NSCoding協(xié)議捐下。不過要做到這一點(diǎn)很容易账锹,可以用Accessorizer類工具自動(dòng)實(shí)現(xiàn)NSCoding協(xié)議。(24.8節(jié)列出了Accessorizer在Mac App Store中的鏈接蔑担。)

下一節(jié)會(huì)解釋預(yù)緩存策略牌废。我們剛才已經(jīng)了解到預(yù)緩存需要用到更結(jié)構(gòu)化的數(shù)據(jù)格式咽白,接下來看看Core Data和SQLite啤握。

2. Core Data

正如Marcus Zarra所說,Core Data更像是一個(gè)對(duì)象序列化框架晶框,而不僅僅是一個(gè)數(shù)據(jù)庫API:

大家誤認(rèn)為Core

Data是一個(gè)Cocoa的數(shù)據(jù)庫API……其實(shí)它是個(gè)可以持久化到磁盤的對(duì)象框架(Zarra排抬,2009年)。

要深入理解Core Data授段,看一下Marcus S. Zarra寫的*Core Data: Apple's API for Persisting Data on Mac OS X*(Pragmatic Bookshelf, 2009. ISBN 9781934356326)蹲蒲。

要在Core Data中保存數(shù)據(jù),首先創(chuàng)建一個(gè)Core Data模型文件侵贵,并創(chuàng)建實(shí)體(Entity)和關(guān)系(Relationship)届搁;然后寫好保存和獲取數(shù)據(jù)的方法。應(yīng)用可以借助Core Data獲取真正的離線訪問功能窍育,就像蘋果內(nèi)置的Mail和Calendar應(yīng)用一樣卡睦。實(shí)現(xiàn)預(yù)緩存時(shí)必須定期刪除不再需要的(過時(shí)的)數(shù)據(jù),否則緩存會(huì)不斷增長并影響應(yīng)用的性能漱抓。同步本地變更是通過追蹤變更集并發(fā)送回服務(wù)器實(shí)現(xiàn)的表锻。變更集的追蹤有很多算法,我推薦的是Git版本控制系統(tǒng)所用的(此處沒有涉及如何與遠(yuǎn)程服務(wù)器同步緩存乞娄,這不在本書討論范圍之內(nèi))瞬逊。

3. 用Core Data實(shí)現(xiàn)按需緩存

盡管從技術(shù)上講可以用Core Data來實(shí)現(xiàn)按需緩存显歧,但我不建議這么做。Core Data的優(yōu)勢是不用反歸檔完整的數(shù)據(jù)就可以獨(dú)立訪問模型的屬性确镊。然而士骤,在應(yīng)用中實(shí)現(xiàn)Core Data帶來的復(fù)雜度抵消了優(yōu)勢。此外蕾域,對(duì)于按需緩存實(shí)現(xiàn)來說敦间,我們可能并不需要獨(dú)立訪問模型的屬性。

4. 原始的SQLite

可以通過鏈接libsqlite3的庫來把SQLite嵌入應(yīng)用束铭,但是這么做有很大的缺陷廓块。所有的sqlite3庫和對(duì)象關(guān)系映射(Object Relational Mapping,ORM)機(jī)制幾乎總是會(huì)比Core Data慢契沫。此外带猴,盡管sqlite3本身是線程安全的,但是iOS上的二進(jìn)制包則不是懈万。所以除非用定制編譯的sqlite3庫(用線程安全的編譯參數(shù)編譯)拴清,否則開發(fā)者就有責(zé)任確保從sqlite3讀取數(shù)據(jù)或者往sqlite3寫入數(shù)據(jù)是線程安全的。Core Data有這么多特性而且內(nèi)置線程安全会通,所以我建議在iOS中盡量避免使用SQLite口予。

唯一應(yīng)該在iOS應(yīng)用中用原始的SQLite而不用Core Data的例外情況是,資源包中有應(yīng)用程序相關(guān)的數(shù)據(jù)需要在所有應(yīng)用支持的第三方平臺(tái)上共享涕侈,比如說運(yùn)行在iPhone沪停、Android、BlackBerry和Windows Phone上的某個(gè)應(yīng)用的位置數(shù)據(jù)庫裳涛。不過這也不是緩存了木张。

5. 應(yīng)該用哪種緩存技術(shù)

在眾多可以本地保存數(shù)據(jù)的技術(shù)中,有三種脫穎而出:URL緩存端三、數(shù)據(jù)模型緩存(利用NSKeyedArchiver)和Core Data舷礼。

假設(shè)你正在開發(fā)一個(gè)應(yīng)用,需要緩存數(shù)據(jù)以改善應(yīng)用表現(xiàn)出的性能郊闯,你應(yīng)該實(shí)現(xiàn)按需緩存(使用數(shù)據(jù)模型緩存或URL緩存)妻献。另一方面,如果需要數(shù)據(jù)能夠離線訪問团赁,而且具有合理的存儲(chǔ)方式以便離線編輯育拨,那么就用高級(jí)序列化技術(shù)(如Core Data)。

6. 數(shù)據(jù)模型緩存與URL緩存

按需緩存可以用數(shù)據(jù)模型緩存或URL緩存來實(shí)現(xiàn)然痊。兩種方式各有優(yōu)缺點(diǎn)至朗,要使用哪一種取決于服務(wù)器的實(shí)現(xiàn)。URL緩存的實(shí)現(xiàn)原理和瀏覽器緩存或代理服務(wù)器緩存類似剧浸。當(dāng)服務(wù)器設(shè)計(jì)得體锹引,遵循HTTP 1.1的緩存規(guī)范時(shí)矗钟,這種緩存效果最好。如果服務(wù)器是SOAP服務(wù)器(或者實(shí)現(xiàn)類似于RPC服務(wù)器或RESTful服務(wù)器)嫌变,就需要用數(shù)據(jù)模型緩存吨艇。如果服務(wù)器遵循HTTP 1.1緩存規(guī)范,就用URL緩存腾啥。數(shù)據(jù)模型緩存允許客戶端(iOS應(yīng)用)掌控緩存失效的情形东涡,當(dāng)開發(fā)者實(shí)現(xiàn)URL緩存時(shí),服務(wù)器通過HTTP 1.1的緩存控制頭控制緩存失效倘待。盡管有些程序員覺得這種方式違反直覺疮跑,而且實(shí)現(xiàn)起來也很復(fù)雜(尤其是在服務(wù)器端),但這可能是實(shí)現(xiàn)緩存的好辦法凸舵。事實(shí)上祖娘,MKNetworkKit提供了對(duì)HTTP 1.1緩存標(biāo)準(zhǔn)的原生支持。

數(shù)據(jù)模型緩存:

本節(jié)我們來給第14章中的iHotelApp添加用數(shù)據(jù)模型緩存實(shí)現(xiàn)的按需緩存啊奄。按需緩存是在視圖從視圖層次結(jié)構(gòu)中消失時(shí)做的(從技術(shù)上講渐苏,是在viewWillDisappear:方法中)。支持緩存的視圖控制器的基本結(jié)構(gòu)如圖24-1所示菇夸。AppCache Architecture的完整代碼可從本章的下載源代碼中找到琼富。后面講解的內(nèi)容假設(shè)你已經(jīng)下載了代碼并且可以隨時(shí)使用。

Image

圖24-1

實(shí)現(xiàn)了按需緩存的視圖控制器的控制流

在viewWillAppear方法中庄新,查看緩存中是否有顯示這個(gè)視圖所需的數(shù)據(jù)鞠眉。如果有就獲取數(shù)據(jù),再用緩存數(shù)據(jù)更新用戶界面摄咆。然后檢查緩存中的數(shù)據(jù)是否已經(jīng)過期凡蚜。你的業(yè)務(wù)規(guī)則應(yīng)該能夠確定什么是新數(shù)據(jù)人断、什么是舊數(shù)據(jù)吭从。如果內(nèi)容是舊的,把數(shù)據(jù)顯示在UI上恶迈,同時(shí)在后臺(tái)從服務(wù)器獲取數(shù)據(jù)并再次更新UI涩金。如果緩存中沒有數(shù)據(jù),顯示一個(gè)轉(zhuǎn)動(dòng)的圓圈表示正在加載暇仲,同時(shí)從服務(wù)器獲取數(shù)據(jù)步做。得到數(shù)據(jù)后,更新UI奈附。

前面的流程圖假定顯示在UI上的數(shù)據(jù)是可以歸檔的模型全度。在iHotelApp的MenuItem模型中實(shí)現(xiàn)NSCoding協(xié)議。NSKeyedArchiver需要模型實(shí)現(xiàn)這個(gè)協(xié)議斥滤,如下面的代碼片段所示将鸵。

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協(xié)議的實(shí)現(xiàn)。

根據(jù)圖24-1中的緩存流程圖顶掉,我們需要在viewWillAppear:中實(shí)現(xiàn)實(shí)際的緩存邏輯草娜。把下面的代碼加入viewWillAppear:就可以實(shí)現(xiàn)。

視圖控制器的viewWillAppear:方法中從緩存恢復(fù)數(shù)據(jù)模型對(duì)象的代碼片段

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

緩存機(jī)制的邏輯流如下所示痒筒。

視圖控制器在歸檔文件MenuItems.archive中檢查之前緩存的項(xiàng)并反歸檔宰闰。

如果MenuItems.archive不存在,視圖控制器調(diào)用方法從服務(wù)器獲取數(shù)據(jù)簿透。

如果MenuItems.archive存在移袍,視圖控制器檢查歸檔文件的修改時(shí)間以確認(rèn)緩存數(shù)據(jù)有多舊。如果數(shù)據(jù)過期了(由業(yè)務(wù)需求決定)老充,再從服務(wù)器獲取一次數(shù)據(jù)咐容。否則顯示緩存的數(shù)據(jù)。

接下來蚂维,把下面的代碼加入viewDidDisappear方法可以把模型(以NSKeyedArchiver的形式)保存在Library/Caches目錄中戳粒。

視圖控制器的viewWillDisappear:方法中緩存數(shù)據(jù)模型的代碼片段

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,

NSUserDomainMask, YES);

NSString *cachesDirectory = [paths objectAtIndex:0];

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

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

視圖消失時(shí)要把menuItems數(shù)組的內(nèi)容保存在歸檔文件中。注意虫啥,如果不是在viewWillAppear:方法中從服務(wù)器獲取數(shù)據(jù)的話蔚约,這種情況不能緩存。

所以涂籽,只需在視圖控制器中加入不到10行的代碼(并將Accessorizer生成的幾行代碼加入模型)苹祟,就可以為應(yīng)用添加緩存支持了。

重構(gòu)

當(dāng)開發(fā)者有多個(gè)視圖控制器時(shí)评雌,前面的代碼可能會(huì)有冗余树枫。我們可以通過抽象出公共代碼并移入名為AppCache的新類來避免冗余。AppCache是處理緩存的應(yīng)用的核心景东。把公共代碼抽象出來放入AppCache可以避免viewWillAppear:和viewWillDisappear:中出現(xiàn)冗余代碼砂轻。

重構(gòu)這部分代碼,使得視圖控制器的viewWillAppear/viewWillDisappear代碼塊看起來如下所示斤吐。加粗部分顯示重構(gòu)時(shí)所做的修改搔涝,我會(huì)在代碼后面解釋。

視圖控制器的viewWillAppear:方法中用AppCache類緩存數(shù)據(jù)模型的重構(gòu)代碼片段(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類把判斷數(shù)據(jù)是否過期的邏輯從視圖控制器中抽象出來了和措,還把緩存保存的位置也抽象出來了庄呈。稍后在本章中我們還會(huì)修改AppCache,再引入一層緩存派阱,內(nèi)容會(huì)保存在內(nèi)存中诬留。

因?yàn)锳ppCache抽象出了緩存的保存位置,我們就不需要為復(fù)制粘貼代碼來獲得應(yīng)用的緩存目錄而操心了。如果應(yīng)用類似于iHotelApp文兑,開發(fā)者可通過為每個(gè)用戶創(chuàng)建子目錄即可輕松增強(qiáng)緩存數(shù)據(jù)的安全性傀广。然后我們就可以修改AppCache中的輔助方法,現(xiàn)在它返回的是緩存目錄彩届,我們可以讓它返回當(dāng)前登錄用戶的子目錄伪冰。這樣,一個(gè)用戶緩存的數(shù)據(jù)就不會(huì)被隨后登錄的用戶看到了樟蠕。

完整的代碼可以從本書網(wǎng)站上本章的源代碼下載中獲取贮聂。

緩存版本控制:

我們在上一節(jié)中寫的AppCache類從視圖控制器中抽象出了按需緩存。當(dāng)視圖出現(xiàn)和消失時(shí)寨辩,緩存就在幕后工作吓懈。然而,當(dāng)你更新應(yīng)用時(shí)靡狞,模型類可能會(huì)發(fā)生變化耻警,這意味著之前歸檔的任何數(shù)據(jù)將不能恢復(fù)到新的模型上。正如之前所講甸怕,對(duì)按需緩存來說甘穿,數(shù)據(jù)并沒有那么重要,開發(fā)者可以刪除數(shù)據(jù)并更新應(yīng)用梢杭。我會(huì)展示可以用來在版本升級(jí)時(shí)刪除緩存目錄的代碼片段温兼。

iOS中驗(yàn)證模型:

第二個(gè)是驗(yàn)證模型,服務(wù)器通常會(huì)發(fā)送一個(gè)校驗(yàn)和(Etag)武契。后續(xù)所有從緩存獲得資源的請求都應(yīng)該用這個(gè)校驗(yàn)和向服務(wù)器**重新驗(yàn)證**資源是否有變化募判。如果校驗(yàn)和匹配,服務(wù)器就返回一個(gè)HTTP 304 Not Modified的狀態(tài)碼咒唆。

IOS內(nèi)存緩存:

目前為止届垫,所有iOS設(shè)備都帶有閃存,而閃存有點(diǎn)小問題:它的讀寫壽命是有限的全释。盡管這個(gè)壽命跟設(shè)備的使用壽命比起來很長装处,但是仍然需要避免過于頻繁地讀寫閃存。在上一個(gè)例子中恨溜,視圖隱藏時(shí)是直接緩存到磁盤的符衔,而視圖顯示時(shí)又是直接從磁盤讀取的。這種行為會(huì)使用戶設(shè)備的緩存負(fù)擔(dān)很重糟袁。為避免這個(gè)問題,我們可以再引入一層緩存躺盛,利用設(shè)備的RAM而不是閃存(用NSMutableDictionary)项戴。在24.2.1節(jié)的“實(shí)現(xiàn)數(shù)據(jù)模型緩存”中,我們介紹了創(chuàng)建歸檔的兩種方法:一個(gè)是保存到文件槽惫,另一個(gè)是保存為NSData對(duì)象周叮。這次會(huì)用到第二個(gè)方法辩撑,我們會(huì)得到一個(gè)NSData指針,將該指針保存到NSMutableDictionary中仿耽,而不是文件系統(tǒng)里的平面文件合冀。引入內(nèi)存緩存的另一個(gè)好處是,在歸檔和反歸檔內(nèi)容時(shí)性能會(huì)略有提升项贺。聽起來很復(fù)雜君躺,實(shí)際上并不復(fù)雜。本節(jié)將介紹如何給AppCache類添加一層透明的开缎、位于內(nèi)存中的緩存棕叫。(“透明”是指調(diào)用代碼,即視圖控制器奕删,甚至不知道這層緩存的存在俺泣,而且也不需要改動(dòng)任何代碼。)我們還會(huì)設(shè)計(jì)一個(gè)LRU(Least Recently Used完残,最近最少使用)算法來把緩存的數(shù)據(jù)保存到磁盤伏钠。

以下簡單列出了要?jiǎng)?chuàng)建內(nèi)存緩存需要的步驟。這些步驟將會(huì)在下面幾節(jié)中詳細(xì)解釋谨设。

添加變量來存放內(nèi)存緩存數(shù)據(jù)贝润。

限制內(nèi)存緩存大小,并且把最近最少使用的項(xiàng)寫入文件铝宵,然后從內(nèi)存緩存中刪除打掘。RAM是有限的,達(dá)到使用極限就會(huì)觸發(fā)內(nèi)存警告鹏秋。收到警告時(shí)不釋放內(nèi)存會(huì)使應(yīng)用崩潰尊蚁。我們當(dāng)然不希望發(fā)生這種事,所以要為內(nèi)存緩存設(shè)置一個(gè)最大閾值侣夷。當(dāng)緩存滿了以后再添加任何東西時(shí)横朋,最近最少使用的對(duì)象應(yīng)該被保存到文件(閃存中)。

處理內(nèi)存警告百拓,并把內(nèi)存緩存以文件形式寫入閃存琴锭。

當(dāng)應(yīng)用關(guān)閉、退出衙传,或進(jìn)入后臺(tái)時(shí)决帖,把內(nèi)存緩存全部以文件形式寫入閃存。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蓖捶,一起剝皮案震驚了整個(gè)濱河市地回,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖刻像,帶你破解...
    沈念sama閱讀 217,907評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件畅买,死亡現(xiàn)場離奇詭異,居然都是意外死亡细睡,警方通過查閱死者的電腦和手機(jī)谷羞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來溜徙,“玉大人湃缎,你說我怎么就攤上這事∶染” “怎么了雁歌?”我有些...
    開封第一講書人閱讀 164,298評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長知残。 經(jīng)常有香客問我靠瞎,道長,這世上最難降的妖魔是什么求妹? 我笑而不...
    開封第一講書人閱讀 58,586評(píng)論 1 293
  • 正文 為了忘掉前任乏盐,我火速辦了婚禮,結(jié)果婚禮上制恍,老公的妹妹穿的比我還像新娘父能。我一直安慰自己,他們只是感情好净神,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評(píng)論 6 392
  • 文/花漫 我一把揭開白布何吝。 她就那樣靜靜地躺著,像睡著了一般鹃唯。 火紅的嫁衣襯著肌膚如雪爱榕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,488評(píng)論 1 302
  • 那天坡慌,我揣著相機(jī)與錄音黔酥,去河邊找鬼。 笑死洪橘,一個(gè)胖子當(dāng)著我的面吹牛跪者,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播熄求,決...
    沈念sama閱讀 40,275評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼渣玲,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了抡四?” 一聲冷哼從身側(cè)響起柜蜈,我...
    開封第一講書人閱讀 39,176評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤仗谆,失蹤者是張志新(化名)和其女友劉穎指巡,沒想到半個(gè)月后淑履,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡藻雪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評(píng)論 3 336
  • 正文 我和宋清朗相戀三年秘噪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勉耀。...
    茶點(diǎn)故事閱讀 39,932評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡指煎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出便斥,到底是詐尸還是另有隱情至壤,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評(píng)論 5 346
  • 正文 年R本政府宣布枢纠,位于F島的核電站像街,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏晋渺。R本人自食惡果不足惜镰绎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望木西。 院中可真熱鬧畴栖,春花似錦、人聲如沸八千。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恋捆。三九已至照皆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鸠信,已是汗流浹背纵寝。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留星立,地道東北人爽茴。 一個(gè)月前我還...
    沈念sama閱讀 48,095評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像绰垂,于是被迫代替她去往敵國和親室奏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • IOS緩存機(jī)制詳解 資料均來自互聯(lián)網(wǎng)劲装,轉(zhuǎn)載時(shí)請務(wù)必以超鏈接形式標(biāo)明文章 原始出處 胧沫、作者信息和本聲明昌简。否則將追究法...
    winsonink閱讀 2,369評(píng)論 3 14
  • 應(yīng)用離線工的兩種方式:第一種是**按需緩存**,這種情況下應(yīng)用緩存起請求應(yīng)答绒怨,就和Web瀏覽器的工作原理一樣纯赎;第二...
    tigger丨閱讀 8,087評(píng)論 0 16
  • *面試心聲:其實(shí)這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個(gè)offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,140評(píng)論 30 470
  • 前言 iOS本地緩存數(shù)據(jù)方式有五種: 1.直接寫文件方式:可以存儲(chǔ)的對(duì)象有NSString、NSArray南蹂、NSD...
    一等到天幻閱讀 1,977評(píng)論 0 1
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理犬金,服務(wù)發(fā)現(xiàn),斷路器六剥,智...
    卡卡羅2017閱讀 134,656評(píng)論 18 139