本地化存储也是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]; }