本文以仿微博的應用為基礎倡蝙,實現(xiàn)使用FMDB做離線緩存
設計思路:
分析加載微博過程:
- 嘗試從沙盒加載緩存數(shù)據(jù)
- 有緩存,直接加載緩存
-
無緩存踢步,發(fā)送請求癣亚,展示返回的數(shù)據(jù),將數(shù)據(jù)存入沙盒
加載微博過程.png
分析微博返回數(shù)據(jù):
- 需要加載的微博多获印,數(shù)據(jù)量大述雾,不適合用plist和NSCoding這類一次性加載和存儲全部數(shù)據(jù)的方法,使用數(shù)據(jù)庫則可以做到取一部分數(shù)據(jù)和存一部分的數(shù)據(jù)
- 由于微博模型多兼丰,如果按照后臺服務器一樣玻孟,一個模型建一張表,每個表(如:用戶鳍征,微博取募,圖片,文字)又相互之間通過外鍵聯(lián)系蟆技,會導致客戶端的數(shù)據(jù)庫過于復雜玩敏,所以只建一張表
- 每條微博的字段非常多,在客戶端數(shù)據(jù)庫表中也創(chuàng)建相應數(shù)量的字段保存數(shù)據(jù)是不適合的质礼,所以可以在數(shù)據(jù)庫表中只創(chuàng)建一個blob類型的status字段保存每條微博的全部數(shù)據(jù)
- 每條微博是根據(jù)自身字段idstr大小來比較新舊的旺聚,把每條微博從數(shù)據(jù)庫取出再取出idstr來比較新舊是不適合的,所以表中還應該增加idstr字段方便查詢
- 最終確定建一張表眶蕉,三個字段:id(系統(tǒng)默認)砰粹,status,idstr
實現(xiàn)步驟
步驟1.創(chuàng)建一個工具類StatusTool
步驟2.在StatusTool的initialize中初始化數(shù)據(jù)庫
static FMDatabase *_db;
+ (void)initialize
{
// 1.打開數(shù)據(jù)庫
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"statuses.sqlite"];
_db = [FMDatabase databaseWithPath:path];
[_db open];
// 2.創(chuàng)表
[_db executeUpdate:@"CREATE TABLE IF NOT EXISTS t_status (id integer PRIMARY KEY, status blob NOT NULL, idstr text NOT NULL);"];
}```
步驟3. StatusTool實現(xiàn)存儲方法
- (void)saveStatuses:(NSArray *)statuses
{
// 要將一個對象存進數(shù)據(jù)庫的blob字段,最好先轉為NSData
// 一個對象要遵守NSCoding協(xié)議,實現(xiàn)協(xié)議中相應的方法,才能轉成NSData
for (NSDictionary *status in statuses) {
// NSDictionary --> NSData
NSData *statusData = [NSKeyedArchiver archivedDataWithRootObject:status];
[_db executeUpdateWithFormat:@"INSERT INTO t_status(status, idstr) VALUES (%@, %@);", statusData, status[@"idstr"]];
}
}```
步驟4. StatusTool實現(xiàn)取緩存方法
+ (NSArray *)statusesWithParams:(NSDictionary *)params
{
// 根據(jù)請求參數(shù)生成對應的查詢SQL語句
NSString *sql = nil;
if (params[@"since_id"]) { // 下拉刷新
sql = [NSString stringWithFormat:@"SELECT * FROM t_status WHERE idstr > %@ ORDER BY idstr DESC LIMIT 20;", params[@"since_id"]];
} else if (params[@"max_id"]) { // 上拉刷新
sql = [NSString stringWithFormat:@"SELECT * FROM t_status WHERE idstr <= %@ ORDER BY idstr DESC LIMIT 20;", params[@"max_id"]];
}
// 執(zhí)行SQL
FMResultSet *set = [_db executeQuery:sql];
NSMutableArray *statuses = [NSMutableArray array];
while (set.next) {
NSData *statusData = [set objectForColumnName:@"status"];
NSDictionary *status = [NSKeyedUnarchiver unarchiveObjectWithData:statusData];
[statuses addObject:status];
}
return statuses;
}```
步驟5.方法調用:
/**
- 下拉刷新造挽,加載最新的數(shù)據(jù)
*/
-
(void)loadNewStatus
{
// 1.拼接請求參數(shù)
碱璃。。饭入。嵌器。。谐丢。爽航。
params[@"since_id"] = firstStatusF.status.idstr;// 2.先嘗試從數(shù)據(jù)庫中加載微博數(shù)據(jù)
NSArray statuses = [StatusTool statusesWithParams:params];
if (statuses.count) { // 數(shù)據(jù)庫有緩存數(shù)據(jù)
/ ……….處理數(shù)據(jù), 展示返回的數(shù)據(jù)*/
} else { // 數(shù)據(jù)庫沒緩存數(shù)據(jù)
// 發(fā)送請求
[HttpTool get:@"https://api.weibo.com/2/statuses/friends_timeline.json" params:params success:^(id json) {
// 緩存新浪返回的字典數(shù)組
[StatusTool saveStatuses:json[@"statuses"]];/* ……….處理數(shù)據(jù) , 展示返回的數(shù)據(jù)*/ } failure:^(NSError *error) { 蚓让。。讥珍。历极。 }];
}
}
/**
- 上拉刷新,加載更多的微博數(shù)據(jù)
*/
-
(void)loadMoreStatus
{
// 1.拼接請求參數(shù)
衷佃。趟卸。。氏义。锄列。。觅赊。
params[@"max_id"] = @(maxId);// 2. 先嘗試從數(shù)據(jù)庫中加載微博數(shù)據(jù)
NSArray statuses = [StatusTool statusesWithParams:params];
if (statuses.count) { // 數(shù)據(jù)庫有緩存數(shù)據(jù)
/ ……….處理數(shù)據(jù), 展示返回的數(shù)據(jù)*/
} else { // 數(shù)據(jù)庫沒緩存數(shù)據(jù)
// 發(fā)送請求
[HttpTool get:@"https://api.weibo.com/2/statuses/friends_timeline.json" params:params success:^(id json) {
// 緩存新浪返回的字典數(shù)組
[StatusTool saveStatuses:json[@"statuses"]];/* ……….處理數(shù)據(jù), 展示返回的數(shù)據(jù)*/ } failure:^(NSError *error) { 。琼稻。吮螺。。帕翻。鸠补。 }];
}
}```
遇到問題:
如果把NSDictionary字典數(shù)據(jù)status直接通過[_db executeUpdateWithFormat:@"INSERT INTO t_status(status, idstr) VALUES (%@, %@);", status, status[@"idstr"]];
來存儲,就會取不出來嘀掸。
原因是通過%@來傳入字典對象紫岩,相當于傳入[status description];
,打印類型可以發(fā)現(xiàn)取出的是字符串睬塌,不是我們預期的字典對象泉蝌。
解決方法:
把字典用NSData *statusData = [NSKeyedArchiver archivedDataWithRootObject:status];
轉成NSData再存入數(shù)據(jù)庫。
取出數(shù)據(jù)時揩晴,用NSDictionary *status = [NSKeyedUnarchiver unarchiveObjectWithData:statusData];
再轉成字典勋陪。
為什么字典對象能轉成NSData類型
字典對象能轉成NSData類型的本質是遵守了NSCoding協(xié)議,所以如果想要把自定義對象轉成NSData類型需要遵守NSCoding協(xié)議硫兰,實現(xiàn)encodeWithCoder和initWithCoder方法
例:自定義對象
@interface Shop : NSObject <NSCoding>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) double price;
@end
@implementation Shop
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:self.name forKey:@"name"];
[encoder encodeDouble:self.price forKey:@"price"];
}
- (id)initWithCoder:(NSCoder *)decoder
{
if (self = [super init]) {
self.name = [decoder decodeObjectForKey:@"name"];
self.price = [decoder decodeDoubleForKey:@"price"];
}
return self;
}
@end```