iOS緩存設(shè)計(jì)(YYCache思路)
前言:
前段時(shí)間業(yè)務(wù)有緩存需求,于是結(jié)合YYCache和業(yè)務(wù)需求,做了緩存層(內(nèi)存&磁盤)+ 網(wǎng)絡(luò)層的方案嘗試
由于YYCache 采用了內(nèi)存緩存和磁盤緩存組合方式,性能優(yōu)良,這里拿它的原理來說下如何設(shè)計(jì)一套緩存的思路,并結(jié)合網(wǎng)絡(luò)整理一套完整流程
目錄
- 初步認(rèn)識(shí)緩存
- 如何優(yōu)化緩存(YYCache設(shè)計(jì)思想)
- 網(wǎng)絡(luò)和緩存同步流程
一、初步認(rèn)識(shí)緩存
1. 什么是緩存?
我們做一個(gè)緩存前,先了解它是什么,緩存是本地?cái)?shù)據(jù)存儲(chǔ),存儲(chǔ)方式主要包含兩種:磁盤儲(chǔ)存和內(nèi)存存儲(chǔ)
1.1 磁盤存儲(chǔ)
磁盤緩存,磁盤也就是硬盤緩存,磁盤是程序的存儲(chǔ)空間,磁盤緩存容量大速度慢,磁盤是永久存儲(chǔ)東西的,iOS為不同數(shù)據(jù)管理對(duì)存儲(chǔ)路徑做了規(guī)范如下:
1、每一個(gè)應(yīng)用程序都會(huì)擁有一個(gè)應(yīng)用程序沙盒。
2、應(yīng)用程序沙盒就是一個(gè)文件系統(tǒng)目錄。
沙盒根目錄結(jié)構(gòu):Documents、Library、temp。
磁盤存儲(chǔ)方式主要有文件管理和數(shù)據(jù)庫(kù),其特性:

1.2 內(nèi)存存儲(chǔ)
內(nèi)存緩存,內(nèi)存緩存是指當(dāng)前程序運(yùn)行空間,內(nèi)存緩存速度快容量小,它是供cpu直接讀取,比如我們打開一個(gè)程序,他是運(yùn)行在內(nèi)存中的,關(guān)閉程序后內(nèi)存又會(huì)釋放。
iOS內(nèi)存分為5個(gè)區(qū):棧區(qū),堆區(qū),全局區(qū),常量區(qū),代碼區(qū)
棧區(qū)stack:這一塊區(qū)域系統(tǒng)會(huì)自己管理,我們不用干預(yù),主要存一些局部變量,以及函數(shù)跳轉(zhuǎn)時(shí)的現(xiàn)場(chǎng)保護(hù)。因此大量的局部變量,深遞歸,函數(shù)循環(huán)調(diào)用都可能導(dǎo)致內(nèi)存耗盡而運(yùn)行崩潰。
堆區(qū)heap:與棧區(qū)相對(duì),這一塊一般由我們自己管理,比如alloc,free的操作,存儲(chǔ)一些自己創(chuàng)建的對(duì)象。
全局區(qū)(靜態(tài)區(qū)static):全局變量和靜態(tài)變量都存儲(chǔ)在這里,已經(jīng)初始化的和沒有初始化的會(huì)分開存儲(chǔ)在相鄰的區(qū)域,程序結(jié)束后系統(tǒng)會(huì)釋放
常量區(qū):存儲(chǔ)常量字符串和const常量
代碼區(qū):存儲(chǔ)代碼
在程序中聲明的容器(數(shù)組 、字典)都可看做內(nèi)存中存儲(chǔ),特性如下:

2. 緩存做什么?
我們使用場(chǎng)景比如:離線加載,預(yù)加載,本地通訊錄...等,對(duì)非網(wǎng)絡(luò)數(shù)據(jù),使用本地?cái)?shù)據(jù)管理的一種,具體使用場(chǎng)景有很多
3. 怎么做緩存?
簡(jiǎn)單緩存可以僅使用磁盤存儲(chǔ),iOS主要提供四種磁盤存儲(chǔ)方式:
-
NSKeyedArchiver: 采用歸檔的形式來保存數(shù)據(jù), 該數(shù)據(jù)對(duì)象需要遵守NSCoding協(xié)議, 并且該對(duì)象對(duì)應(yīng)的類必須提供encodeWithCoder:和initWithCoder:方法.
//自定義Person實(shí)現(xiàn)歸檔解檔
//.h文件
#import <Foundation/Foundation.h>
@interface Person : NSObject<NSCoding>
@property(nonatomic,copy) NSString * name;
@end
//.m文件
#import "Person.h"
@implementation Person
//歸檔要實(shí)現(xiàn)的協(xié)議方法
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:_name forKey:@"name"];
}
//解檔要實(shí)現(xiàn)的協(xié)議方法
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
_name = [aDecoder decodeObjectForKey:@"name"];
}
return self;
}
@end
使用歸檔解檔
// 將數(shù)據(jù)存儲(chǔ)在path路徑下歸檔文件
[NSKeyedArchiver archiveRootObject:p toFile:path];
// 根據(jù)path路徑查找解檔文件
Person *p = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
缺點(diǎn):歸檔的形式來保存數(shù)據(jù),只能一次性歸檔保存以及一次性解壓。所以只能針對(duì)小量數(shù)據(jù),如果想改動(dòng)數(shù)據(jù)的某一小部分,需要解壓整個(gè)數(shù)據(jù)或者歸檔整個(gè)數(shù)據(jù)。
-
NSUserDefaults: 用來保存應(yīng)用程序設(shè)置和屬性、用戶保存的數(shù)據(jù)。用戶再次打開程序或開機(jī)后這些數(shù)據(jù)仍然存在。
NSUserDefaults可以存儲(chǔ)的數(shù)據(jù)類型包括:NSData、NSString、NSNumber、NSDate、NSArray、 NSDictionary。
// 以鍵值方式存儲(chǔ)
[[NSUserDefaults standardUserDefaults] setObject:@"value" forKey:@"key"];
// 以鍵值方式讀取
[[NSUserDefaults standardUserDefaults] objectForKey:@"key"];
-
Write寫入方式:永久保存在磁盤中。具體方法為:
//將NSData類型對(duì)象data寫入文件,文件名為FileName
[data writeToFile:FileName atomically:YES];
//從FileName中讀取出數(shù)據(jù)
NSData *data=[NSData dataWithContentsOfFile:FileName options:0 error:NULL];
-
SQLite:采用SQLite數(shù)據(jù)庫(kù)來存儲(chǔ)數(shù)據(jù)。SQLite作為?一中小型數(shù)據(jù)庫(kù),應(yīng)用ios中跟其他三種保存方式相比,相對(duì)復(fù)雜一些
//打開數(shù)據(jù)庫(kù)
if (sqlite3_open([databaseFilePath UTF8String], &database)==SQLITE_OK) {
NSLog(@"sqlite dadabase is opened.");
} else { return;}//打開不成功就返回
//在打開了數(shù)據(jù)庫(kù)的前提下,如果數(shù)據(jù)庫(kù)沒有表,那就開始建表了哦!
char *error;
const char *createSql="create table(id integer primary key autoincrement, name text)"; if (sqlite3_exec(database, createSql, NULL, NULL, &error)==SQLITE_OK) {
NSLog(@"create table is ok.");
} else {
sqlite3_free(error);//每次使用完畢清空error字符串,提供給下?一次使用
}
// 建表完成之后, 插入記錄
const char *insertSql="insert into a person (name) values(‘gg’)";
if (sqlite3_exec(database, insertSql, NULL, NULL, &error)==SQLITE_OK) {
NSLog(@"insert operation is ok.");
} else {
sqlite3_free(error);//每次使用完畢清空error字符串,提供給下一次使用
}
上面提到的磁盤存儲(chǔ)特性,具備空間大、可持久、但是讀取慢,面對(duì)大量數(shù)據(jù)頻繁讀取時(shí)更加明顯,以往測(cè)試中磁盤讀取比內(nèi)存讀取保守測(cè)量低于幾十倍,那我們?cè)趺唇鉀Q磁盤讀取慢的缺點(diǎn)呢? 又如何利用內(nèi)存的優(yōu)勢(shì)呢?
二、 如何優(yōu)化緩存(YYCache設(shè)計(jì)思想)
YYCache背景知識(shí):
源碼中由兩個(gè)主要類構(gòu)成

- YYMemoryCache (內(nèi)存緩存)
操作YYLinkedMap中數(shù)據(jù), 為實(shí)現(xiàn)內(nèi)存優(yōu)化,采用雙向鏈表數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn) LRU算法,YYLinkedMapItem 為每個(gè)子節(jié)點(diǎn) - YYDiskCache (磁盤緩存)
不會(huì)直接操作緩存對(duì)象(sqlite/file),而是通過 YYKVStorage 來間接的操作緩存對(duì)象。
容量管理:
- ageLimit :時(shí)間周期限制,比如每天或每星期開始清理
- costLimit: 容量限制,比如超出10M后開始清理內(nèi)存
- countLimit : 數(shù)量限制, 比如超出1000個(gè)數(shù)據(jù)就清理
這里借用YYCache設(shè)計(jì), 來講述緩存優(yōu)化
1. 磁盤+內(nèi)存組合優(yōu)化
利用內(nèi)存和磁盤特性,融合各自優(yōu)點(diǎn),整合如下:

- APP會(huì)優(yōu)先請(qǐng)求內(nèi)存緩沖中的資源
- 如果內(nèi)存緩沖中有,則直接返回資源文件, 如果沒有的話,則會(huì)請(qǐng)求資源文件,這時(shí)資源文件默認(rèn)資源為本地磁盤存儲(chǔ),需要操作文件系統(tǒng)或數(shù)據(jù)庫(kù)來獲取。
- 獲取到的資源文件,先緩存到內(nèi)存緩存,方便以后不再重復(fù)獲取,節(jié)省時(shí)間。
然后就是從緩存中取到數(shù)據(jù)然后給app使用。
這樣就充分結(jié)合兩者特性,利用內(nèi)存讀取快特性減少讀取數(shù)據(jù)時(shí)間,
YYCache 源碼解析:
- (id<NSCoding>)objectForKey:(NSString *)key {
// 1.如果內(nèi)存緩存中存在則返回?cái)?shù)據(jù)
id<NSCoding> object = [_memoryCache objectForKey:key];
if (!object) {
// 2.若不存在則查取磁盤緩存數(shù)據(jù)
object = [_diskCache objectForKey:key];
if (object) {
// 3.并將數(shù)據(jù)保存到內(nèi)存中
[_memoryCache setObject:object forKey:key];
}
}
return object;
}
2. 內(nèi)存優(yōu)化-- 提高內(nèi)存命中率
但是我們想在基礎(chǔ)上再做優(yōu)化,比如想讓經(jīng)常訪問的數(shù)據(jù)保留在內(nèi)存中,提高內(nèi)存的命中率,減少磁盤的讀取,那怎么做處理呢? -- LRU算法

LRU算法:我們可以將鏈表看成一串?dāng)?shù)據(jù)鏈,每個(gè)數(shù)據(jù)是這個(gè)串上的一個(gè)節(jié)點(diǎn),經(jīng)常訪問的數(shù)據(jù)移動(dòng)到頭部,等數(shù)據(jù)超出容量后從鏈表后面的一些節(jié)點(diǎn)銷毀,這樣經(jīng)常訪問數(shù)據(jù)在頭部位置,還保留在內(nèi)存中。
鏈表實(shí)現(xiàn)結(jié)構(gòu)圖:

YYCache 源碼解析
/**
A node in linked map.
Typically, you should not use this class directly.
*/
@interface _YYLinkedMapNode : NSObject {
@package
__unsafe_unretained _YYLinkedMapNode *_prev; // retained by dic
__unsafe_unretained _YYLinkedMapNode *_next; // retained by dic
id _key;
id _value;
NSUInteger _cost;
NSTimeInterval _time;
}
@end
@implementation _YYLinkedMapNode
@end
/**
A linked map used by YYMemoryCache.
It's not thread-safe and does not validate the parameters.
Typically, you should not use this class directly.
*/
@interface _YYLinkedMap : NSObject {
@package
CFMutableDictionaryRef _dic; // do not set object directly
NSUInteger _totalCost;
NSUInteger _totalCount;
_YYLinkedMapNode *_head; // MRU, do not change it directly
_YYLinkedMapNode *_tail; // LRU, do not change it directly
BOOL _releaseOnMainThread;
BOOL _releaseAsynchronously;
}
/// Insert a node at head and update the total cost.
/// Node and node.key should not be nil.
- (void)insertNodeAtHead:(_YYLinkedMapNode *)node;
/// Bring a inner node to header.
/// Node should already inside the dic.
- (void)bringNodeToHead:(_YYLinkedMapNode *)node;
/// Remove a inner node and update the total cost.
/// Node should already inside the dic.
- (void)removeNode:(_YYLinkedMapNode *)node;
/// Remove tail node if exist.
- (_YYLinkedMapNode *)removeTailNode;
/// Remove all node in background queue.
- (void)removeAll;
@end
_YYLinkedMapNode *_prev 為該節(jié)點(diǎn)的頭指針,指向前一個(gè)節(jié)點(diǎn)
_YYLinkedMapNode *_next為該節(jié)點(diǎn)的尾指針,指向下一個(gè)節(jié)點(diǎn)
頭指針和尾指針將一個(gè)個(gè)子節(jié)點(diǎn)串連起來,形成雙向鏈表
來看下bringNodeToHead:的源碼實(shí)現(xiàn),它是實(shí)現(xiàn)LRU算法主要方法,移動(dòng)node子結(jié)點(diǎn)到鏈頭。
(詳細(xì)已注釋在代碼中)
- (void)bringNodeToHead:(_YYLinkedMapNode *)node {
if (_head == node) return; // 如果當(dāng)前節(jié)點(diǎn)是鏈頭,則不需要移動(dòng)
// 鏈表中存了兩個(gè)指向鏈頭(_head)和鏈尾(_tail)的指針,便于鏈表訪問
if (_tail == node) {
_tail = node->_prev; // 若當(dāng)前節(jié)點(diǎn)為鏈尾,則更新鏈尾指針
_tail->_next = nil; // 鏈尾的尾節(jié)點(diǎn)這里設(shè)置為nil
} else {
// 比如:A B C 鏈表, 將 B拿走,將A C重新聯(lián)系起來
node->_next->_prev = node->_prev; // 將node的下一個(gè)節(jié)點(diǎn)的頭指針指向node的上一個(gè)節(jié)點(diǎn),
node->_prev->_next = node->_next; // 將node的上一個(gè)節(jié)點(diǎn)的尾指針指向node的下一個(gè)節(jié)點(diǎn)
}
node->_next = _head; // 將當(dāng)前node節(jié)點(diǎn)的尾指針指向之前的鏈頭,因?yàn)榇藭r(shí)node為最新的第一個(gè)節(jié)點(diǎn)
node->_prev = nil; // 鏈頭的頭節(jié)點(diǎn)這里設(shè)置為nil
_head->_prev = node; // 之前的_head將為第二個(gè)節(jié)點(diǎn)
_head = node; // 當(dāng)前node成為新的_head
}
其他方法就不挨個(gè)舉例了,具體可翻看源碼,這些代碼結(jié)構(gòu)清晰,類和函數(shù)遵循單一職責(zé),接口高內(nèi)聚,低耦合,是個(gè)不錯(cuò)的學(xué)習(xí)示例!
3. 磁盤優(yōu)化 - 數(shù)據(jù)分類存儲(chǔ)
YYDiskCache 是一個(gè)線程安全的磁盤緩存,基于 sqlite 和 file 來做的磁盤緩存,我們的緩存對(duì)象可以自由的選擇存儲(chǔ)類型,
下面簡(jiǎn)單對(duì)比一下:
- sqlite: 對(duì)于小數(shù)據(jù)(例如 NSNumber)的存取效率明顯高于 file。
- file: 對(duì)于較大數(shù)據(jù)(例如高質(zhì)量圖片)的存取效率優(yōu)于 sqlite。
所以 YYDiskCache 使用兩者配合,靈活的存儲(chǔ)以提高性能。
另外:
YYDiskCache 具有以下功能:
- 它使用 LRU(least-recently-used) 來刪除對(duì)象。
- 支持按 cost,count 和 age 進(jìn)行控制。
- 它可以被配置為當(dāng)沒有可用的磁盤空間時(shí)自動(dòng)驅(qū)逐緩存對(duì)象。
- 它可以自動(dòng)抉擇每個(gè)緩存對(duì)象的存儲(chǔ)類型(sqlite/file)以便提供更好的性能表現(xiàn)。
YYCache源碼解析
// YYKVStorageItem 是 YYKVStorage 中用來存儲(chǔ)鍵值對(duì)和元數(shù)據(jù)的類
// 通常情況下,我們不應(yīng)該直接使用這個(gè)類
@interface YYKVStorageItem : NSObject
@property (nonatomic, strong) NSString *key; ///< key
@property (nonatomic, strong) NSData *value; ///< value
@property (nullable, nonatomic, strong) NSString *filename; ///< filename (nil if inline)
@property (nonatomic) int size; ///< value's size in bytes
@property (nonatomic) int modTime; ///< modification unix timestamp
@property (nonatomic) int accessTime; ///< last access unix timestamp
@property (nullable, nonatomic, strong) NSData *extendedData; ///< extended data (nil if no extended data)
@end
/**
YYKVStorage 是基于 sqlite 和文件系統(tǒng)的鍵值存儲(chǔ)。
通常情況下,我們不應(yīng)該直接使用這個(gè)類。
@warning
這個(gè)類的實(shí)例是 *非* 線程安全的,你需要確保
只有一個(gè)線程可以同時(shí)訪問該實(shí)例。如果你真的
需要在多線程中處理大量的數(shù)據(jù),應(yīng)該分割數(shù)據(jù)
到多個(gè) KVStorage 實(shí)例(分片)。
*/
@interface YYKVStorage : NSObject
#pragma mark - Attribute
@property (nonatomic, readonly) NSString *path; /// storage 路徑
@property (nonatomic, readonly) YYKVStorageType type; /// storage 類型
@property (nonatomic) BOOL errorLogsEnabled; /// 是否開啟錯(cuò)誤日志
#pragma mark - Initializer
- (nullable instancetype)initWithPath:(NSString *)path type:(YYKVStorageType)type NS_DESIGNATED_INITIALIZER;
#pragma mark - Save Items
- (BOOL)saveItem:(YYKVStorageItem *)item;
...
#pragma mark - Remove Items
- (BOOL)removeItemForKey:(NSString *)key;
...
#pragma mark - Get Items
- (nullable YYKVStorageItem *)getItemForKey:(NSString *)key;
...
#pragma mark - Get Storage Status
- (BOOL)itemExistsForKey:(NSString *)key;
- (int)getItemsCount;
- (int)getItemsSize;
@end
我們只需要看一下 YYKVStorageType 這個(gè)枚舉,它決定著 YYKVStorage 的存儲(chǔ)類型。
YYKVStorageType
/**
存儲(chǔ)類型,指示“YYKVStorageItem.value”存儲(chǔ)在哪里。
@discussion
通常,將數(shù)據(jù)寫入 sqlite 比外部文件更快,但是
讀取性能取決于數(shù)據(jù)大小。在測(cè)試環(huán)境 iPhone 6s 64G,
當(dāng)數(shù)據(jù)較大(超過 20KB)時(shí)從外部文件讀取數(shù)據(jù)比 sqlite 更快。
*/
typedef NS_ENUM(NSUInteger, YYKVStorageType) {
YYKVStorageTypeFile = 0, // value 以文件的形式存儲(chǔ)于文件系統(tǒng)
YYKVStorageTypeSQLite = 1, // value 以二進(jìn)制形式存儲(chǔ)于 sqlite
YYKVStorageTypeMixed = 2, // value 將根據(jù)你的選擇基于上面兩種形式混合存儲(chǔ)
};
總結(jié):
這里說了YYCache幾個(gè)主要設(shè)計(jì)優(yōu)化之處,其實(shí)細(xì)節(jié)上也有很多不錯(cuò)的處理,比如:
線程安全
如果說 YYCache 這個(gè)類是一個(gè)純邏輯層的緩存類(指 YYCache 的接口實(shí)現(xiàn)全部是調(diào)用其他類完成),那么 YYMemoryCache 與 YYDiskCache 還是做了一些事情的(并沒有 YYCache 當(dāng)甩手掌柜那么輕松),其中最顯而易見的就是 YYMemoryCache 與 YYDiskCache 為 YYCache 保證了線程安全。
YYMemoryCache 使用了 pthread_mutex 線程鎖來確保線程安全,而 YYDiskCache 則選擇了更適合它的 dispatch_semaphore,上文已經(jīng)給出了作者選擇這些鎖的原因。性能
YYCache 中對(duì)于性能提升的實(shí)現(xiàn)細(xì)節(jié):
- 異步釋放緩存對(duì)象
- 鎖的選擇
- 使用 NSMapTable 單例管理的 YYDiskCache
- YYKVStorage 中的 _dbStmtCache
- 甚至使用 CoreFoundation 來?yè)Q取微乎其微的性能提升
3. 網(wǎng)絡(luò)和緩存同步流程
結(jié)合網(wǎng)絡(luò)層和緩存層,設(shè)計(jì)了一套接口緩存方式,比較靈活且速度得到提升; 比如首頁(yè)界面可能由多個(gè)接口提供數(shù)據(jù),沒有采用整塊存儲(chǔ)而是將存儲(chǔ)細(xì)分到每個(gè)接口中,有API接口控制,基本結(jié)構(gòu)如下:
主要分為:
- 應(yīng)用層 :顯示數(shù)據(jù)
- 管理層: 管理網(wǎng)絡(luò)層和緩存層,為應(yīng)用層提供數(shù)據(jù)支持
- 網(wǎng)絡(luò)層: 請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù)
- 緩存層: 緩存數(shù)據(jù)
層級(jí)圖:

- 服務(wù)端每套數(shù)據(jù)對(duì)應(yīng)一個(gè)version (或時(shí)間戳),若后臺(tái)數(shù)據(jù)發(fā)生變更,則version發(fā)生變化,在返回客戶端數(shù)據(jù)時(shí)并將version一并返回。
- 當(dāng)客戶端請(qǐng)求網(wǎng)絡(luò)時(shí),將本地上一次數(shù)據(jù)對(duì)應(yīng)version上傳。
- 服務(wù)端獲取客戶端傳來得version后,與最新的version進(jìn)行對(duì)比,若version不一致,則返回最新數(shù)據(jù),若未發(fā)生變化,服務(wù)端不需要返回全部數(shù)據(jù)只需返回304(No Modify) 狀態(tài)值
- 客戶端接到服務(wù)端返回?cái)?shù)據(jù),若返回全部數(shù)據(jù)非304,客戶端則將最新數(shù)據(jù)同步到本地緩存中;客戶端若接到304狀態(tài)值后,表示服務(wù)端數(shù)據(jù)和本地?cái)?shù)據(jù)一致,直接從緩存中獲取顯示
這也是ETag的大致流程;詳細(xì)可以查看 https://baike.baidu.com/item/ETag/4419019?fr=aladdin
源碼示例
- (void)getDataWithPage:(NSNumber *)page pageSize:(NSNumber *)pageSize option:(DataSourceOption)option completion:(void (^)(HomePageListCardModel * _Nullable, NSError * _Nullable))completionBlock {
NSString *cacheKey = CacheKey(currentUser.userId, PlatIndexRecommendation);// 全局靜態(tài)常量 (userid + apiName)
// 根據(jù)需求而定是否需要緩存方式,網(wǎng)絡(luò)方式走304邏輯
switch (option) {
case DataSourceCache:
{
if ([_cache containsObjectForKey:cacheKey]) {
completionBlock((HomePageListCardModel *)[self->_cache objectForKey:cacheKey], nil);
} else {
completionBlock(nil, LJDError(400, @"緩存中不存在"));
}
}
break;
case DataSourceNetwork:
{
[NetWorkServer requestDataWithPage:page pageSize:pageSize completion:^(id _Nullable responseObject, NSError * _Nullable error) {
if (responseObject && !error) {
HomePageListCardModel *model = [HomePageListCardModel yy_modelWithJSON:responseObject];
if (model.errnonumber == 304) { //取緩存數(shù)據(jù)
completionBlock((HomePageListCardModel *)[self->_cache objectForKey:cacheKey], nil);
} else {
completionBlock(model, error);
[self->_cache setObject:model forKey:cacheKey]; //保存到緩存中
}
} else {
completionBlock(nil, error);
}
}];
}
break;
default:
break;
}
}
這樣做好處:
- 對(duì)于不頻繁更新數(shù)據(jù)的接口,節(jié)省了大量JSON數(shù)據(jù)轉(zhuǎn)化時(shí)間
- 節(jié)約流量,節(jié)省加載時(shí)長(zhǎng)
- 用戶界面顯示加快
總結(jié):項(xiàng)目中并不一定完全這樣做,有時(shí)候過渡設(shè)計(jì)也是一種浪費(fèi),多了解其他設(shè)計(jì)思路后,針對(duì)項(xiàng)目找到適合的才是最好的!
參考文獻(xiàn):
YYCache: https://github.com/ibireme/YYCache
YYCache 設(shè)計(jì)思路 :https://blog.ibireme.com/2015/10/26/yycache/