上一节(一一五)利用NSKeyedArchiver实现随意对象转为二进制介绍了将随意对象转化为二进制数据和还原的方法。可用于实现本节介绍的微博数据离线缓存。
通过新浪官方的API能够发现,返回的微博数据例如以下样式:
{ "statuses": [ { "created_at": "Tue May 31 17:46:55 +0800 2011", "id": 11488058246, "text": "求关注。当中的statuses是微博字典数组,微博client通过字典数组转为微博模型WeiboStatus,然后通过微博模型创建微博尺寸模型StatusFrame模型,最后Cell通过尺寸模型StatusFrame拿到应该显示的内容和尺寸。这是client实现的功能。", "source": "<a href="http://weibo.com" rel="nofollow">新浪微博</a>", "favorited": false, "truncated": false, "in_reply_to_status_id": "", "in_reply_to_user_id": "", "in_reply_to_screen_name": "", "geo": null, "mid": "5612814510546515491", "reposts_count": 8, "comments_count": 9, "annotations": [], "user": { "id": 1404376560, "screen_name": "zaku", "name": "zaku", "province": "11", "city": "5", "location": "北京 朝阳区", "description": "人生五十年,乃如梦如幻;有生斯有死,壮士复何憾。
", "url": "http://blog.sina.com.cn/zaku", "profile_image_url": "http://tp1.sinaimg.cn/1404376560/50/0/1", "domain": "zaku", "gender": "m", "followers_count": 1204, "friends_count": 447, "statuses_count": 2908, "favourites_count": 0, "created_at": "Fri Aug 28 00:00:00 +0800 2009", "following": false, "allow_all_act_msg": false, "remark": "", "geo_enabled": true, "verified": false, "allow_all_comment": true, "avatar_large": "http://tp1.sinaimg.cn/1404376560/180/0/1", "verified_reason": "", "follow_me": false, "online_status": 0, "bi_followers_count": 215 } }, ... ], "ad": [ { "id": 3366614911586452, "mark": "AB21321XDFJJK" }, ... ], "previous_cursor": 0, // 暂未支持 "next_cursor": 11488013766, // 暂未支持 "total_number": 81655 }
获取数据的关键,在于statuses的抓取上,通常情况下我们向新浪的server发送请求,接收到响应体,取出statuses字典数组后进行处理。如今我们要实现的是进入client时先推断有没有缓存过数据。缓存过则直接显示已经缓存的数据。而不直接通过网络请求。
离线缓存通过iOS自带的SQLite3实现。
关于数据库的使用请參考文章使用FMDB操作SQLite数据库,本文重点是缓存思路。
微博有一个唯一标志idstr用于标识微博的先后顺序,idstr越大的微博越新,我们的缓存思路是把微博字典数组中的每一条微博都用NSKeyedArchiver转化为二进制存入数据库的status字段,然后再把idstr存储到idstr字段,通过对idstr排序、和当前要求的id范围比較就能够正确推断出数据的新旧关系。
在给新浪发送数据时,假设是载入新数据。会在參数中增加一个since_id,表示仅仅载入这个id之后的微博。假设是载入旧数据,会在參数中增加一个max_id,表示仅仅载入这个之前的。
因此在缓存数据时,仅仅接收微博字典数组就可以。在读取缓存时,传入请求參数,推断參数中有无since_id、max_id就可以。缓存方法均为类方法,为了能拿到数据库,使用一个静态成员变量管理。而且在类的公共初始化方法中打开数据库,假设没有创建过表则创建。
数据库的打开:
static FMDatabase *_db; + (void)initialize{ // 公共类初始化方法 NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"statuses.sqlite"]; _db = [FMDatabase databaseWithPath:path]; [_db open]; [_db executeUpdate:@"CREATE TABLE IF NOT EXISTS t_status (id integer PRIMARY KEY, status blob NOT NULL, idstr text NOT NULL)"]; }
缓存的方法:
+ (NSArray *)statusesWithParams:(NSDictionary *)params{ 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"]]; }else{ // 无条件载入最前面20条 sql = [NSString stringWithFormat:@"SELECT * FROM t_status ORDER BY idstr DESC LIMIT 20"]; } // 运行查询 FMResultSet *set = [_db executeQuery:sql]; NSMutableArray *statuses = [NSMutableArray array]; while (set.next) { NSData *dictData = [set objectForColumnName:@"status"]; NSDictionary *dict = [NSKeyedUnarchiver unarchiveObjectWithData:dictData]; [statuses addObject:dict]; } return statuses; } + (void)saveStatuses:(NSArray *)statuses{ for (NSDictionary *statusDict in statuses) { NSData *dictData = [NSKeyedArchiver archivedDataWithRootObject:statusDict]; [_db executeUpdateWithFormat:@"INSERT INTO t_status (status, idstr)VALUES(%@,%@)",dictData,statusDict[@"idstr"]]; } }
在运行网络请求时。调用读取缓存的方法。假设得到的数组中有元素,则不进行网络请求。而是对缓存的字典转模型。然后进一步操作,这样就完毕了缓存。