看这篇文章之前你得:
- 已经了解了基本的SQL语法, 基本会使用
- 推荐一个桌面数据库软件: Navigate Premium(有需要的留言)
- 知道移动端开发, SQLite是干什么的
直接把我xcode中的文件考过来了, 已经写得很详细了, (可以直接考到xcode中, 这样就能按分类来看)耐心看吧…
import UIKit
class SQLiteManager: NSObject {
static let shareInstance: SQLiteManager = SQLiteManager()
/*数据库基本操作:
1. 数据库文件
2. 新建表
3. 指定字段名称
4. 添加记录
*/
/// 创建数据库文件
var db: COpaquePointer = nil
// MARK:- 打开或创建数据库文件(一般就在appdelegate中打开一次就行了)
func openDB(dbName: String) -> Bool {
// 1. 生成db文件的路径, 没有就会创建一个
let path = dbName.cacheDir()
guard let cPath = path.cStringUsingEncoding(NSUTF8StringEncoding) else {
return false
}
// 2. 创建数据库文件
// 专门用户打开数据库文件, 如果不存在系统会自动创建一个新的
// 第一个参数: 数据库文件的路径
// 第二个参数: 数据库的指针(句柄), 只要打开成功系统就会将打开的数据库赋值给该指针
// 后续所有关于数据库的操作, 都依赖于该指针
// sqlite3_open方法会返回一个整型的值, 告诉我们是否打开成功
if sqlite3_open(cPath, &db) != SQLITE_OK {
return false
}
// 3. 新建表
if !createTable() {
return false
}
return true
}
// MARK:- 创建表
func createTable() -> Bool {
// 1.定义sql语句(可以在navcat中写好考进来, 换行写, 一目了然)
let sqlStr = "CREATE TABLE 'T_Person' (" +
"'name' TEXT," +
"'age' INTEGER," +
"'id' INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT" +
");"
// 执行创建表的sql语句
/*
第1个参数: 一个已经打开的数据库
第2个参数: 需要执行的SQL语句
第3个参数: 执行SQL之后的回调
第4个参数: 第三个参数的第一个参数
第5个参数: 错误信息的指针, 如果执行过程中发生错误就会给传入的指针赋值
*/
if sqlite3_exec(db, sqlStr.cStringUsingEncoding(NSUTF8StringEncoding)!, nil, nil, nil) != SQLITE_OK {
return false
}
return true
}
// MARK:- 执行sql语句
/*
第1个参数: 一个已经打开的数据库
第2个参数: 需要执行的SQL语句
第3个参数: 执行SQL之后的回调
第4个参数: 第三个参数的第一个参数
第5个参数: 错误信息的指针, 如果执行过程中发生错误就会给传入的指针赋值
*/
/*
写入: sql语句
let sqlStr = "INSERT INTO T_Person \n" +
"(name, age) \n" +
"VALUES \n" +
"('\(name!)', \(age));"
*/
func execSQL(sqlStr: String) -> Bool {
if sqlite3_exec(db, sqlStr.cStringUsingEncoding(NSUTF8StringEncoding)!, nil, nil, nil) != SQLITE_OK {
return false
}
return true
}
// MARK:- 执行查询语句
/*
查询语句(条件语句的使用where)
let sqlStr = "SELECT id, name, age FROM T_Person;"
*/
func querySQL(sqlStr: String) -> [[String: AnyObject]]? {
// 1. 预编译
/*
第一个参数: 一个已经打开的数据库
第二个参数: 需要执行的SQL语句
第三个参数: 需要执行的SQL语句的长度, 传入-1系统会自动计算
第四个参数: 结果集指针(以后所有获取数据的操作, 都依赖于该指针)
第五个参数: 没用过
*/
// 结果集, 查新结果都依赖此
var stmt: COpaquePointer = nil
if sqlite3_prepare_v2(db, sqlStr.cStringUsingEncoding(NSUTF8StringEncoding)!, -1, &stmt, nil) != SQLITE_OK {
return nil
}
// 2. 利用结果集取出查询结果
// 只要是调用一次sqlite3_step方法, 系统就会利用stmt取出一条数据
// 如果该方法的返回值等于SQLITE_ROW, 就代表可以获取数据
var array = [[String: AnyObject]]()
while sqlite3_step(stmt) == SQLITE_ROW {
// 1. 获取当前一共多少列
let count = sqlite3_column_count(stmt)
// 定义一条空数据信息, 用于保存
var dict = [String: AnyObject]()
for i in 0..<count {
// 2. 获取每一列的名称
let cName = sqlite3_column_name(stmt, i)
let name = String(CString: cName, encoding: NSUTF8StringEncoding)!
// 3. 获取每一列的数据类型
let type = sqlite3_column_type(stmt, i)
switch type
{
case SQLITE_INTEGER:
let intValue = sqlite3_column_int(stmt, i)
dict[name] = Int(intValue)
case SQLITE_FLOAT:
let floatValue = sqlite3_column_double(stmt, i)
dict[name] = floatValue
case SQLITE_BLOB:
print("blob") // blob是二进制数据, 一般不做sqlite数据存储对象
case SQLITE_NULL:
dict[name] = NSNull()
case SQLITE_TEXT:
let cTextValue = UnsafePointer<Int8>(sqlite3_column_text(stmt, i))
let textValue = String(CString: cTextValue, encoding: NSUTF8StringEncoding)
dict[name] = textValue
default:
print("other")
}
}
array.append(dict)
}
return array
}
// MARK:- 数据库的事务处理(防止多线程不安全)
/*
1.事务能够保证多条SQL语句, 要么一起成功, 要么一起失败
2.想要保证多条SQL语句一起成功或者一起失败, 就必须在执行多条SQL语句之前开启事物
只有遇到了提交事务, 数据库才会修改数据
3.如果中途遇到了意外, 我们可以通过回滚事物来还原以前的数据
4.提高性能
*/
// - sqlite3执行sql语句是默认开启事务处理的, 当我们多条数据时, 应手动开启事务, 这样就只要开启一次, 提升性能
// - 重复的开启和提交是非常非常消耗性能的, 所以为了避免这种问题, 我们可以自己开启事物, 只要我们自己开了事物 ,系统就不会自动帮我们开启事物了
// 1. 开启事务
func beginTransaction() -> Bool {
return execSQL("BEGIN TRANSACTION;")
}
// 2. 提交事务
func commit() -> Bool {
return execSQL("COMMIT TRANSACTION;")
}
// 3. 回滚事务
func rollBack() -> Bool {
return execSQL("ROLLBACK TRANSACTION;")
}
// MARK:- 数据写入之动态绑定插入, 提高性能, 多数SQLite框架都是采用这种方式来写入数据, FMDB也是这么干的
private let SQLITE_TRANSIENT = unsafeBitCast(-1, sqlite3_destructor_type.self)
func insert(sql: String, args: CVarArgType...) -> Bool
{
var stmt: COpaquePointer = nil
// 1.预编译SQL语句
if sqlite3_prepare_v2(db, sql.cStringUsingEncoding(NSUTF8StringEncoding)!, -1, &stmt, nil) != SQLITE_OK
{
print("预编译失败")
return false
}
// 2.绑定参数
var index: Int32 = 1
for objc in args
{
if objc is Int
{
/*
第一个参数: stmt对象
第二个参数: 需要绑定的字段的索引
注意: 字段索引从1开始
第三个参数: 需要绑定到SQL上的值
*/
sqlite3_bind_int64(stmt, index, sqlite3_int64(objc as! Int))
}else if objc is Double
{
sqlite3_bind_double(stmt, index, objc as! Double)
}else if objc is String
{
/*
第一个参数: stmt对象
第二个参数: 需要绑定的字段的索引
第三个参数: 需要绑定到SQL上的值
第四个参数: 没用过
第五个参数: bind方法中如何处理传入的字符串
处理方式有两种: 1.方法内部不管传入的字符串, 不会对传入的字符串进行retain(copy)操作, 所以在使用该字符串时字符串可能已经释放了, 此时系统就会随便设置一个值
2.方法内部会管理传入的字符串, 所以在使用字符串时字符串不会被释放, 使用完毕之后bind方法内部会自动释放该字符串
#define SQLITE_STATIC ((sqlite3_destructor_type)0) 上述1
#define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1) 上述2
*/
let text = objc as! String
let cText = text.cStringUsingEncoding(NSUTF8StringEncoding)!
sqlite3_bind_text(stmt, index, cText, -1, SQLITE_TRANSIENT)
}
index++
}
// 3.执行SQL语句
if sqlite3_step(stmt) != SQLITE_DONE
{
print("执行SQL语句失败")
return false
}
// 4.重置STMT
sqlite3_reset(stmt)
// 5.关闭STMT
sqlite3_finalize(stmt)
// 6.返回结果
return true
}
// MARK:- sqlite异步执行
// 1. 创建一个串行队列
let queue = dispatch_queue_create("sqliteQueue", DISPATCH_QUEUE_SERIAL)
/// 2. 异步执行SQL语句
func execSQLQueue(action: (db: COpaquePointer) -> ()) {
dispatch_async(queue) { () -> Void in
action(db: self.db)
// 在闭包中执行SQL语句
}
}
}