sqlite3基本用法(FMDB底层)详解

看这篇文章之前你得:

  • 已经了解了基本的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语句
        }
    }
    
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,039评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,089评论 4 62
  • 从前车马很慢,书信很远,一生很长。 锦先生说:明年还不知道结果如何。 远处有炊烟袅袅,身后是夕阳。 回家的时候会路...
    南小欠阅读 378评论 0 1
  • 人生如此,说易不易,说难不难。只要每个人,都乐观开心的向前看,人生就是如此简单!
    萝莘莘阅读 135评论 0 0
  • 听闻Swift已良久,然一直也没能静下心来好好系统的学习这门超酷的语言,话不多说,下面正式开始Swift之旅。 一...
    沫简影阅读 197评论 1 1