利用数据库存储是ios进行数据持久化的三个方法之一,由于sqlite的轻量、易用而备受欢迎,现在我们可以将数据库的一些操作进行封装,方便以后重用。
——————————————————————————————————————
一、Sqlite基础
SQLite是一种关系型数据库,因为其轻量易操作而成为当前移动客户端的主流数据库。使用SQLite可以存储大量数据并且易于对数据进行管理、查询。
在SQLite中有以下几种基本类型
TEXT:文本型
INTEGER:整形
REAL:实数型
BLOB:二进制数据
NULL:空值
以上是5种“基本数据类型”,其实sqlite3也可以接受其他的一些复杂类型,例如date、time等。不过在ios开发中完全可以用TEXT代替任意类型,非常方便。
另外,SQLite采用动态数据类型,即可以根据存入的值进行自动的判断,换句话说,在建表时完全可以将类型忽略,不过为了可读性,我们一般不会省略数据类型,同时也不建议省略。
二、使用sqlite进行开发
要利用sqlite就得先将其导入到项目中,添加sqlite对应的库libsqlite3.dylib
然后在用到的文件中引入头文件
<span style="font-size:14px;">#import <sqlite3.h> </span>
添加好以后就可以利用sqlite的相关函数进行操作了。sqlite为我们提供了不少方法,不过这些方法都是C层级的,常用的几个类型和方法有:
//sqlite常用的数据类型与方法 ADT SQLite { attribute: sqlite3 *sqlite; sqlite3_stmt *stmt; methods: //打开数据库,这是数据库操作的第一步,第一个参数代表文件名,第二个参数为数据库句柄指针的地址 void sqlite3_open(const char *filename, sqlite3 **ppDb); //编译数据库语句,为查询做准备,第二个参数为sql语句(UTF-8),第三个为长度,第四个是stmt类型变量 int sqlite3_prepare(sqlite3 *db,const char *zSql,int nByte,sqlite3_stmt **ppStmt,const char **pzTail); //为占位符绑定数据,根据绑定的数据类型不同调用的函数也不同 //之前也说过ios中多用text,所以这里将绑定text的函数搬上来 //前三个参数分别代表stmt变量、被绑定占位符的位置(从1开始)、值(UTF-8) int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n, void(*)(void*)); //当sql语句被绑定好数据后就可以调用该函数执行该语句了,这个其实是对很多操作的封装,可以方便的执行语句。不过注意此函数执行的是非查询操作 int sqlite3_exec(sqlite3*, const char *sql, sqlite_callback, void*, char**); //“执行”编译好的sql语句并返回结果,有查询结果将并返回SQLITE_ROW,查询中如果所有行查询完毕后返回SQLITE_DONE,如果有错误则返回SQLITE_ERROR int sqlite3_step(sqlite3_stmt*); //销毁一个stmt,在关闭数据库前必须进行此操作。 int sqlite3_finalize(sqlite3_stmt*); }
通常,我们在用数据库的时候会先用open函数建表,然后实现其插入、删除、修改、查询等功能。实现方式也比较简单,加之部分与后文重复,这里就不暂时不给出实现代码了。现在我们来将这些方法封装一下,以后可以更加方便的使用数据库。
三、封装数据库操作
数据库的操作无非有建表、插入、删除、修改、查询。尽管每种数据模型每次操作的数据库语句都不一样,但是其他的操作基本上是一致的,我们可以将数据库语句做参数,将对应的功能进行封装。
首先获取沙盒目录,以后会在此建表。
<span style="font-size:14px;">#define kFileName @"RSSReader.sqlite" - (NSString *)filePath { NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]; NSString *filePath = [file stringByAppendingPathComponent:kFileName]; //NSLog(@"%@", file); return filePath; }</span>
打开数据库——错误处理——处理数据库语句——错误处理——关闭数据库——完成。
在以上步骤中不同表的不同之处仅在于数据库语句,因此将其作为参数。如下
- (void)createTable:(NSString *)sqlString { sqlite3 *sqlite = nil; int result = sqlite3_open([self.filePath UTF8String], &sqlite); if (result != SQLITE_OK) { //NSLog(@"打开数据库失败"); sqlite3_close(sqlite); } char *errMsg; result = sqlite3_exec(sqlite, [sqlString UTF8String], NULL, NULL, &errMsg); if (result != SQLITE_OK) { //NSLog(@"创建表失败:%s", errMsg); sqlite3_close(sqlite); } //NSLog(@"创建表成功"); sqlite3_close(sqlite); }
打开数据库——错误处理——编译SQL语句——错误处理
——绑定数据并执行SQL语句——关闭数据库——完成。
具体实现代码:
- (BOOL)dealSql:(NSString *)sqlString params:(NSArray *)params { sqlite3 *sqlite = nil; sqlite3_stmt *stmt = nil; //打开数据库 int result = sqlite3_open([self.filePath UTF8String], &sqlite); if (result != SQLITE_OK) { //NSLog(@"打开数据库失败"); sqlite3_close(sqlite); return NO; } //编译SQL语句 result = sqlite3_prepare(sqlite, [sqlString UTF8String], -1, &stmt, NULL); if (result != SQLITE_OK) { //NSLog(@"数据库语句编译失败"); sqlite3_close(sqlite); return NO; } //绑定数据并执行sql语句 for (int i = 0; i < params.count; i++) { NSString *value = [params objectAtIndex:i]; sqlite3_bind_text(stmt, i + 1, [value UTF8String], -1, NULL); } result = sqlite3_step(stmt); if (result == SQLITE_ERROR) { //NSLog(@"数据库操作失败"); sqlite3_close(sqlite); return NO; } //关闭数据库 sqlite3_finalize(stmt); sqlite3_close(sqlite); return YES; }
然后留给用户三个接口:
<span style="font-size:14px;">/**插入数据, params:字符串数组*/ - (BOOL)insertData:(NSString *)sqlString params:(NSArray *)params; /**删除数据, params:字符串数组*/ - (BOOL)deleteData:(NSString *)sqlString params:(NSArray *)params; /**更改数据, params:字符串数组*/ - (BOOL)modifyData:(NSString *)sqlString params:(NSArray *)params;</span>
<span style="font-size:14px;">- (BOOL)deleteData:(NSString *)sqlString params:(NSArray *)params { return [self dealSql:sqlString params:params]; } - (BOOL)insertData:(NSString *)sqlString params:(NSArray *)params { return [self dealSql:sqlString params:params]; } - (BOOL)modifyData:(NSString *)sqlString params:(NSArray *)params { return [self dealSql:sqlString params:params]; }</span>
刚才也说过,之所以把查询单拿出来,是因为它跟上述操作有所不同——必须知道你的SQL语句中要查询变量的个数,并且使用step函数,然后通过该函数的返回结果决定下一步操作。具体来说,如果step返回SELECT_ROW则说明查询到一条数据,可以继续调用step函数接着查询,如果返回SELECT_ERROR说明有错误,如果返回SELECT_DONE说明查询结束,利用循环语句可以实现这一功能。
代码如下:
<span style="font-size:14px;">- (NSMutableArray *)selectData:(NSString *)sqlString columnCount:(NSInteger)count { sqlite3 *sqlite = nil; sqlite3_stmt *stmt = nil; //打开数据库 int result = sqlite3_open([self.filePath UTF8String], &sqlite); if (result != SQLITE_OK) { //NSLog(@"打开数据库失败"); sqlite3_close(sqlite); return nil; } //编译SQL语句 result = sqlite3_prepare(sqlite, [sqlString UTF8String], -1, &stmt, NULL); if (result != SQLITE_OK) { //NSLog(@"数据库语句编译失败"); sqlite3_close(sqlite); return nil; } NSMutableArray *resultArray = [NSMutableArray array]; result = sqlite3_step(stmt); while (result == SQLITE_ROW) { NSMutableArray *everyData = [NSMutableArray array]; for (int i = 0; i < count; i++) { NSString *everyColumn = [NSString stringWithCString:(char *)sqlite3_column_text(stmt, i) encoding:NSUTF8StringEncoding]; [everyData addObject:everyColumn]; } [resultArray addObject:everyData]; result = sqlite3_step(stmt); } //关闭数据库 sqlite3_finalize(stmt); sqlite3_close(sqlite); return resultArray; }</span>
四、使用封装好的库
既然已经将这些操作都封装好了,那就实际操作一下吧。这里拿我自己的RSS应用来说明。
其实刚才的类只是一个“抽象类”,因为并没有实际的sql语句。再想象一下,每一个表对应的sql语句不同,所以我们可以继承这个抽象类,加入自己的方法来提供sql语句。例如在RSS应用中,我一共设了两个表:存RSS源的表和存每个RSS源下文章的RSSList表。这里用RSS表来举例,接口文件中定义如下:
<span style="font-size:14px;">@class RSSModel; @interface RSSSqlite : ICESqlite +(id)sharedInstance; - (void)createRSSTable; - (BOOL)addRSS:(RSSModel *)model; - (BOOL)deleteRSS:(RSSModel *)model; - (NSMutableArray *)selectRSS;</span>
.m:
<span style="font-size:14px;">- (void)createRSSTable { NSString *sql = @"CREATE TABLE IF NOT EXISTS RSSArtical(link TEXT primary key,name TEXT,date TEXT,category TEXT,imageUrlString TEXT)"; [self createTable:sql]; } - (BOOL)addRSS:(RSSModel *)model { NSString *sql = @"INSERT INTO RSSArtical(link,name,date,category,imageUrlString) VALUES(?,?,?,?,?)"; NSArray *array = [NSArray arrayWithObjects:model.link, model.name, model.date, model.category, model.imageUrlString, nil]; return [self insertData:sql params:array]; } - (BOOL)deleteRSS:(RSSModel *)model { NSString *sql = @"DELETE FROM RSSArtical WHERE link=?"; NSArray *array = [NSArray arrayWithObject:model.link]; return [self deleteData:sql params:array]; } - (NSMutableArray *)selectRSS { NSString *sql = @"SELECT link,name,date,category,imageUrlString FROM RSSArtical"; NSArray *data = [self selectData:sql columnCount:5]; NSMutableArray *rssArticals = [NSMutableArray array]; for (NSArray *array in data) { NSString *link = array[0]; NSString *name = array[1]; NSString *date = array[2]; NSString *category = array[3]; NSString *imageUrlString = array[4]; RSSModel *model = [[RSSModel alloc] initWithlink:link name:name date:date category:category imageUrlString:imageUrlString]; [rssArticals addObject:model]; } return rssArticals; }</span>
<span style="font-size:14px;">if ([[NSUserDefaults standardUserDefaults] boolForKey:@"firstRSS"]) { //NSLog(@"第一次登陆,创建RSS表"); [[RSSSqlite sharedInstance] createRSSTable]; [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"firstRSS"]; }</span>