本地化存储也是app开发当中比较常见的功能需求。比如一些列表界面(tableview)相关的数据存储。
本文就以tableview界面数据的存储为例。
为简单起见,demo中使用了MJExtension及MJRefresh框架,采用常用的MVC代码结构,我们将在此基础上扩展其本地化存储功能。列表界面的核心代码如下(灰色背景的部分表示将要实现和扩展的方法):
1 - (void)setupRefresh { 2 3 self.tableView.mj_header = [XXRefreshHeader headerWithRefreshingTarget:self refreshingAction:@selector(loadNewTopics)]; 4 5 [self.tableView.mj_header beginRefreshing]; 6 7 self.tableView.mj_footer = [XXRefreshFooter footerWithRefreshingTarget:self refreshingAction:@selector(loadMoreTopics)]; 8 9 } 10 11 #pragma mark - 数据加载 12 13 - (void)loadNewTopics { 14 15 // 取消所有请求 16 17 [self.manager.tasks makeObjectsPerformSelector:@selector(cancel)]; 18 19 // 参数 20 21 NSMutableDictionary *params = [NSMutableDictionary dictionary]; 22 23 params[@"a"] = self.aParam; 24 25 params[@"type"] = @(self.type); 26 27 __weak typeof(self) weakSelf = self; 28 29 // 发送请求 30 31 [self.manager GET:XXCommonURL parameters:params success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) { 32 33 // 存储maxtime(方便用来加载下一页数据) 34 35 weakSelf.maxtime = responseObject[@"info"][@"maxtime"]; 36 37 // 字典数组 -> 模型数组 38 39 weakSelf.topics = [XXTopic mj_objectArrayWithKeyValuesArray:responseObject[@"list"]]; 40 41 // 2. 在本地sql做缓存 todo 42 43 [weakSelf saveToSql: weakSelf.topics ] ; 44 45 // 刷新表格 46 47 [weakSelf.tableView reloadData]; 48 49 // 让[刷新控件]结束刷新 50 51 [weakSelf.tableView.mj_header endRefreshing]; 52 53 } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { 54 55 // 让[刷新控件]结束刷新 56 57 [weakSelf.tableView.mj_header endRefreshing]; 58 59 // 加载缓存、本地数据库 todo 60 61 weakSelf.topics = [weakSelf getFromeSql]; 62 63 // 刷新表格 64 65 [weakSelf.tableView reloadData]; 66 67 }]; 68 69 } 70 71 72 73 - (void)loadMoreTopics { 74 75 // 取消所有的请求 76 77 [self.manager.tasks makeObjectsPerformSelector:@selector(cancel)]; 78 79 // 参数 80 81 NSMutableDictionary *params = [NSMutableDictionary dictionary]; 82 83 params[@"a"] = self.aParam; 84 85 params[@"maxtime"] = self.maxtime; 86 87 params[@"type"] = @(self.type); 88 89 __weak typeof(self) weakSelf = self; 90 91 // 发送请求 92 93 [self.manager GET:XXCommonURL parameters:params success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) { 94 95 // 存储这页对应的maxtime 96 97 weakSelf.maxtime = responseObject[@"info"][@"maxtime"]; 98 99 // 字典数组 -> 模型数组 100 101 NSArray<XMGTopic *> *moreTopics = [XXTopic mj_objectArrayWithKeyValuesArray:responseObject[@"list"]]; 102 103 [weakSelf.topics addObjectsFromArray:moreTopics]; 104 105 // 插入新增的数据至数据库 106 107 [[SQLiteManager shareSQLiteManager] saveTopics:moreTopics]; 108 109 // 刷新表格 110 111 [weakSelf.tableView reloadData]; 112 113 114 115 // 让[刷新控件]结束刷新 116 117 [weakSelf.tableView.mj_footer endRefreshing]; 118 119 } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { 120 121 // 让[刷新控件]结束刷新 122 123 [weakSelf.tableView.mj_footer endRefreshing]; 124 125 }]; 126 127 } 128 129 #pragma mark - Table view data source 130 131 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 132 133 return self.topics.count; 134 135 } 136 137 138 139 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 140 141 XXTopicCell *cell = [tableView dequeueReusableCellWithIdentifier:XMGTopicCellId]; 142 143 cell.topic = self.topics[indexPath.row]; 144 145 return cell; 146 147 } 148 149 #pragma mark - 代理方法 150 151 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 152 153 { 154 155 return self.topics[indexPath.row].cellHeight; 156 157 }
这是面向模型的开发模式,模型类相关属性如下(局部):
@interface XXTopic : NSObject /** 用户的名字 */ @property (nonatomic, copy) NSString *name; /** 用户的头像 */ @property (nonatomic, copy) NSString *profile_image; /** 帖子的文字内容 */ @property (nonatomic, copy) NSString *text; /***** 额外增加的属性 - 方便开发 *****/ /** cell的高度 */ @property (nonatomic, assign) CGFloat cellHeight; /** 中间内容的frame */ @property (nonatomic, assign) CGRect contentF; @end
其实如果单纯的实现存储的功能,简单粗暴的方法就是直接将模型、或者原始的字典或数组整体进行存储。但是这样的话就不方便相关的统计、搜索,后期需要扩展这些功能的话笨重而不灵活。
所以这里就将模型属性一一对应进行sql的数据存储。设计一个工具类SQLiteManager,核心代码如下(本次旨在实现功能,代码还可以优化重构,或采用运行时机制减少侵入性):
#import "SQLiteManager.h"
#import <sqlite3.h>
#import "XXTopic.h"
@interface SQLiteManager ()
{ sqlite3 *db;}
@end
@implementation SQLiteManager
+ (instancetype)shareSQLiteManager {
static SQLiteManager *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[SQLiteManager alloc] init];
});
return instance;
}
- (instancetype)init
{
if (self = [super init]) {
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject;
NSString *filePath = [path stringByAppendingPathComponent:@"demo.sqlite"];
NSLog(@"%@", filePath);
if (sqlite3_open([filePath UTF8String], &db) == SQLITE_OK)
{ NSLog(@"打开成功");
[self createTable];
}
}
return self;
}
- (BOOL)createTable {
NSString *sql = @"CREATE TABLE IF NOT EXISTS t_topic(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, profile_image TEXT, text TEXT, cellHeight FLOAT);";
return [self execSQL:sql];
}
- (BOOL)dropTable {
NSString *sql = @"drop table if exists t_stu;";
return [self execSQL:sql];
}
- (BOOL)execSQL:(NSString *)sql {
return sqlite3_exec(db, [sql UTF8String], NULL, NULL, NULL) == SQLITE_OK;
}
- (void)saveTopics:(NSArray *)topics {
for(XXTopic* topic in topics) {
NSString *sql = [NSString stringWithFormat:@"INSERT INTO t_topic(name, profile_image, text, cellHeight) VALUES('%@', '%@', '%@', %f);", topic.name, topic.profile_image, topic.text, topic.cellHeight];
sqlite3_stmt *stmt = nil;
if (sqlite3_prepare_v2(db, [sql UTF8String], -1, &stmt, nil) != SQLITE_OK) return;
if (sqlite3_step(stmt) == SQLITE_DONE){
NSLog(@"插入一条记录成功!");
}
sqlite3_finalize(stmt);
}
}
// 返回模型数组
- (NSArray *)getTopics {
NSString *sql = @"select * from t_topic;";
// 准备语句
sqlite3_stmt *stmt = nil;
if (sqlite3_prepare_v2(db, [sql UTF8String], -1, &stmt, nil) != SQLITE_OK) {
NSLog(@"准备语句创建失败!");
return nil;
}
NSMutableArray *arrM = [NSMutableArray array];
while (sqlite3_step(stmt) == SQLITE_ROW) {
int count = sqlite3_column_count(stmt);
id value;
for (int i = 0; i < count; ++i) {
const char *cName = sqlite3_column_name(stmt, i);
NSString *columnNameStr = [NSString stringWithCString:cName encoding:NSUTF8StringEncoding];
NSLog(@"ddddddd%@",columnNameStr);
int type = sqlite3_column_type(stmt, i);
switch (type) {
case SQLITE_INTEGER:
{ int value = sqlite3_column_int(stmt, i);
}
break;
case SQLITE_FLOAT:
{ double value = sqlite3_column_double(stmt, i);
}
break;
case SQLITE3_TEXT:
{
const char *textValue = sqlite3_column_text(stmt, i);
value = [NSString stringWithCString:textValue encoding:NSUTF8StringEncoding];
}
break;
case SQLITE_NULL:
break;
default:
break;
}
XXTopic *topicItem = [[XXTopic alloc]init];
if ([columnNameStr isEqualToString: @"name"]) {
topicItem.name = (NSString *)value;
}
if ([columnNameStr isEqualToString: @"profile_image"]) {
topicItem.profile_image = (NSString *)value;
}
if ([columnNameStr isEqualToString: @"text"]) {
topicItem.text = (NSString *)value;
}
if ([columnNameStr isEqualToString: @"cellHeight"]) {
topicItem.cellHeight = [value doubleValue];
}
[arrM addObject:topicItem];
}
}
return [arrM copy];
}
@end
现在就可以实现存储的这两个方法了。
- (NSArray *)getFromeSql {
if (topics != nil) return ;
return [[SQLiteManager shareSQLiteManager] getTopics];
}
- (void)saveToSql:(NSArray *)topics {
[[SQLiteManager shareSQLiteManager] dropTable];
//删除表后需要重新创建表单
[[SQLiteManager shareSQLiteManager] createTable] ;
[[SQLiteManager shareSQLiteManager] saveTopics:weakSelf.topics];
}