(写动画库有点烦,等我写好了,会放到git上,到时候放网址。)
SQLite是嵌入式的和轻量级的sql数据库。广泛用于包括浏览器、ios、android以及一些便携需求的小型web应用系统。
但是它也是关系型数据库,所以在功能上是要强于coreData的。
一、离线缓存
在项目开发中,通常都需要对数据进行离线缓存的处理,就像我上一篇说的,新闻类就很需要这样做。
离线缓存一般都是把数据保存到项目的沙盒中。有以下几种方式
(1)归档:NSCodeing、NSKeyedArchiver(我上一篇说过了)
(2)偏好设置:NSUserDefaults
(3)Plist存储:writeToFile
所以,这种情况下,上面的三种就没法应付了。
好了 想用它 自然先导库
libsqlite3.dylib, 3是第三个版本的意思。推荐一个博文你们阅读下
由于原生是C语言格式,所以我们先讲原生,然后给大家讲个三方库,很强大,开发时经常用到。
先引用库。#import "sqlite3.h"
然后创建数据库,如果没有就创建(sqlite3_open):
为了避免重复创建,所以在创建前先判断,不过说到这里我想起来了,强调一下,之前一直说 SQLite3不是线程安全,所以所以 一般情况都是加上锁。但是最近我看了一篇博文,之后发现其实是是支持的。等文章最后再说。
<span style="font-size:18px;">sqlite3* database_;
-(BOOL) open{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:@"mydb.sql"];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL find = [fileManager fileExistsAtPath:path];
//找到数据库文件mydb.sql
if (find) {
NSLog(@"Database file have already existed.");
if(sqlite3_open([path UTF8String], &database_) != SQLITE_OK) {
sqlite3_close(database_);
NSLog(@"Error: open database file.");
return NO;
}
return YES;
}
if(sqlite3_open([path UTF8String], &database_) == SQLITE_OK) {
bFirstCreate_ = YES;
[self createChannelsTable:database_];//在后面实现函数createChannelsTable
return YES;
} else {
sqlite3_close(database_);
NSLog(@"Error: open database file.");
return NO;
}
return NO;
}</span>
创建表格:
<span style="font-size:18px;">//创建表格,假设有五个字段,(id,cid,title,imageData ,imageLen )
//说明一下,id为表格的主键,必须有。
//cid,和title都是字符串,imageData是二进制数据,imageLen 是该二进制数据的长度。
- (BOOL) createChannelsTable:(sqlite3*)db
{
char *sql = "CREATE TABLE channels (id integer primary key, \
cid text, \
title text, \
imageData BLOB, \
imageLen integer)";
sqlite3_stmt *statement;
if(sqlite3_prepare_v2(db, sql, -1, &statement, nil) != SQLITE_OK) {
NSLog(@"Error: failed to prepare statement:create channels table");
return NO;
}
int success = sqlite3_step(statement);
sqlite3_finalize(statement);
if ( success != SQLITE_DONE) {
NSLog(@"Error: failed to dehydrate:CREATE TABLE channels");
return NO;
}
NSLog(@"Create table 'channels' successed.");
return YES;
}</span>
插入数据:
<span style="font-size:18px;">- (BOOL) insertOneChannel:(Channel*)channel
{
NSData* ImageData = UIImagePNGRepresentation( channel.image_);
NSInteger Imagelen = [ImageData length];
sqlite3_stmt *statement;
static char *sql = "INSERT INTO channels (cid,title,imageData,imageLen)\
VALUES(?,?,?,?)";
//问号的个数要和(cid,title,imageData,imageLen)里面字段的个数匹配,代表未知的值,将在下面将值和字段关联。
int success = sqlite3_prepare_v2(database_, sql, -1, &statement, NULL);
if (success != SQLITE_OK)
{
NSLog(@"Error: failed to insert:channels");
return NO;
}
//这里的数字1,2,3,4代表第几个问号
sqlite3_bind_text(statement, 1, [channel.id_ UTF8String], -1, SQLITE_TRANSIENT);
sqlite3_bind_text(statement, 2, [channel.title_ UTF8String], -1, SQLITE_TRANSIENT);
sqlite3_bind_blob(statement, 3, [ImageData bytes], Imagelen, SQLITE_TRANSIENT);
sqlite3_bind_int(statement, 4, Imagelen);
success = sqlite3_step(statement);
sqlite3_finalize(statement);
if (success == SQLITE_ERROR) {
NSLog(@"Error: failed to insert into the database with message.");
return NO;
}
NSLog(@"Insert One Channel#############:id = %@",channel.id_);
return YES;
}</span>
查询数据:
- (void) getChannels:(NSMutableArray*)fChannels
{
sqlite3_stmt *statement = nil;
char *sql = "SELECT * FROM channels";
if (sqlite3_prepare_v2(database_, sql, -1, &statement, NULL) != SQLITE_OK)
{
NSLog(@"Error: failed to prepare statement with message:get channels.");
}
//查询结 果集中一条一条的遍历所有的记录,这里的数字对应的是列值。
while (sqlite3_step(statement) == SQLITE_ROW)
{
char* cid = (char*)sqlite3_column_text(statement, 1);
char* title = (char*)sqlite3_column_text(statement, 2);
Byte* imageData = (Byte*)sqlite3_column_blob(statement, 3);
int imageLen = sqlite3_column_int(statement, 4);
Channel* channel = [[Channel alloc] init];
if(cid)
channel.id_ = [NSString stringWithUTF8String:cid];
if(title)
channel.title_ = [NSString stringWithUTF8String:title];
if(imageData)
{
UIImage* image = [UIImage imageWithData:[NSData dataWithBytes:imageData length:imageLen]];
channel.image_ = image;
}
[fChannels addObject:channel];
[channel release];
}
sqlite3_finalize(statement);
}
上面的方法不用说,就是看着不舒服,所以,我给大家推荐个第三方库,FMDB
毕竟第三方库,做的真的很好。FMDB下载
通过FMDB,你可以定制你自己的DBManager,我做了一个简单的,直接上代码,很容易懂。
这是我的.h,自己定的一些接口,完成增删查改的功能。
<span style="font-size:18px;">//
// DBManager.h
// FMDBDemo
//
// Created by JackYang on 15/9/8.
// Copyright (c) 2015年 JackYang. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "PersonModel.h"
@interface DBManager : NSObject
+(instancetype)sharedInstance;
- (void)addPerson:(PersonModel*)model;
- (void)deletePerson:(PersonModel*)model;
- (void)updatePerson:(PersonModel*)model;
- (NSArray*)allPerson;
- (BOOL)isPersonExists:(PersonModel*)model;
@end</span>
然后是.m的实现,由于我只是写了一个Demo,所以只是做了一个PersonModel,实现就是增加,删除person,你们可以自己创建自己想要的表,定制自己的sql语句,w3school 有sql教程。不会的可以去看看。
<span style="font-size:18px;">//
// DBManager.m
// FMDBDemo
//
// Created by JackYang on 15/9/8.
// Copyright (c) 2015年 JackYang. All rights reserved.
//
#import "DBManager.h"
#import "FMDatabase.h"
@interface DBManager ()
//定义数据库,FMDatabase 来描述操作数据库
@property(nonatomic)FMDatabase *db;
@end
@implementation DBManager
//数据库本质也是一个文件,DBMS 来进行管理
+(instancetype)sharedInstance
{
static DBManager *s_dbManager = nil;
// //@synchronized(self) 内部实现时加锁,防止多线程不安全
// @synchronized(self){
// if (s_dbManager == nil) {
// s_dbManager = [[DBManager alloc]init];
// }
// return s_dbManager;
// }
//两种方式可以创建单例。但是这是共享单例,你可以选择吧init隐藏,就是换个方法名,不对外公开。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
s_dbManager = [[DBManager alloc]init];
});
return s_dbManager;
}
- (id)init
{
if (self = [super init]) {
//创建数据库,在数据库中创建表
NSString *dbFilePath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/Person.db"];
NSLog(@"DB Path = %@",dbFilePath);
//使用数据库文件路径,初始化db实例
self.db = [[FMDatabase alloc]initWithPath:dbFilePath];
//打开数据库,如果数据库文件不存在,FMDB自动创建一个,并打开数据库
if(![self.db open]){
NSLog(@"打开数据库失败");
}else{
[self createPersonTable];
}
}
return self;
}
- (void)createPersonTable
{
NSString *sql = @"CREATE TABLE IF NOT EXISTS Persons(PersonId INTEGER PRIMARY KEY AUTOINCREMENT,Age INTEGER,FirstName TEXT,LastName TEXT,Address TEXT,City varchar(255),HeadImage BLOB)";
//使用FMDB时,对sql语句的操作
//executeQuery 针对select 语句
//[self.db executeQuery:<#(NSString *), ...#>];
//update ,delete,insert 使用executeUpdate
if (![self.db executeUpdate:sql]) {
NSLog(@"创建Person表失败");
[self.db close];
};
}
- (void)addPerson:(PersonModel*)model
{
//?代表是占位符,该位置会填充相应的值,注意:传入的值一定是id类型,如果是整数,浮点数等,需要转成NSNumber
NSString *sql = @"INSERT INTO Persons(Age,FirstName,LastName,Address,City,HeadImage) VALUES(?,?,?,?,?,?)";
//UIImagePNGRepresentation 把图片转成NSData的数据
NSData *imageData = UIImagePNGRepresentation(model.headImage);
BOOL result = [self.db executeUpdate:sql,@(model.age),model.firstName,model.lastName,model.address,model.city,imageData];
if(result == NO){
NSLog(@"插入数据失败");
}
}
- (void)deletePerson:(PersonModel*)model
{
NSString *sql = @"DELETE FROM Persons WHERE PersonId = ?";
BOOL result = [self.db executeUpdate:sql,@(model.personId)];
if(result == NO){
NSLog(@"插入数据失败");
}
}
- (void)updatePerson:(PersonModel*)model
{
NSString *sql = @"UPDATE Persons SET Age = ?,FirstName = ?,LastName = ?,Address = ?,City = ? WHERE PersonId = ?";
BOOL result = [self.db executeUpdate:sql,@(model.age),model.firstName,model.lastName,model.address,model.city,@(model.personId)];
if (!result) {
NSLog(@"更新记录失败");
}
}
- (NSArray*)allPerson
{
NSMutableArray *resultArray = [NSMutableArray array];
NSString *sql = @"SELECT * FROM Persons";
FMResultSet *resultSet = [self.db executeQuery:sql];
//resultSet.next 指向的是一条记录
while (resultSet.next) {
PersonModel *person = [[PersonModel alloc]init];
person.personId = [resultSet intForColumn:@"personId"];
person.age = [resultSet intForColumn:@"Age"];
person.firstName = [resultSet stringForColumn:@"FirstName"];
person.lastName = [resultSet stringForColumn:@"LastName"];
person.address = [resultSet stringForColumn:@"Address"];
person.city = [resultSet stringForColumn:@"City"];
NSData *imageData = [resultSet dataForColumn:@"HeadImage"];
person.headImage = [UIImage imageWithData:imageData];
[resultArray addObject:person];
}
[resultSet close];
return resultArray;
}
- (BOOL)isPersonExists:(PersonModel*)model
{
BOOL result = NO;
NSString *sql = @"SELECT * FROM Persons WHERE personId = ?";
FMResultSet *resultSet = [self.db executeQuery:sql,@(model.personId)];
if (resultSet.next) {
result = YES;
}
[resultSet close];
return result;
}
@end</span>
在FMDB中 增删改 都是使用的 executeUpdate,只有 查使用的时executeQuery,这里要注意点
而且,在查的时候 也是和原生一样 使用结果集,进行遍历。
剩下的是就是使用你的工具类,存储你的数据了
<span style="font-size:18px;">- (IBAction)getAllPerson:(id)sender {
self.personList = [[DBManager sharedInstance] allPerson];
[self.tableView reloadData];
}
- (IBAction)addPerson:(id)sender {
PersonModel *person = [[PersonModel alloc]init];
person.age = 20;
person.firstName = @"123";
person.lastName = @"456";
person.address = @"china";
person.city = @"HeFei";
person.headImage = [UIImage imageNamed:@"colors@2x.gif"];
[[DBManager sharedInstance] addPerson:person];
}
- (IBAction)deletePerson:(id)sender {
PersonModel *person = [self.personList objectAtIndex:0];
[[DBManager sharedInstance] deletePerson:person];
}
- (IBAction)updatePerson:(id)sender {
PersonModel *person = [self.personList objectAtIndex:0];
person.firstName = @"aaa";
person.lastName = @"bbb";
[[DBManager sharedInstance] updatePerson:person];
}
</span>
好了,还有什么没有说?
没错,就是原生sql线程安全问题。
这里我就不搬别人的博文了,你们自己去看
好吧,就这些了,啥不懂得 可以问我。