步骤一:加入 SQLite 库 在 swift 中,如果需要使用 OC 的库,必须添加桥接文件。
其中,libsqlite3.dylib 是 OC 写的 sqlite 库
然后在 Build Settings 中搜索 Bridge 配置如下:
步骤二:创建 SQLiteManager 类,该类实现便捷的数据库语句操作(增删改) 创建 SQLiteManager 类,设定其为单粒。
初始化 SQLiteManager 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 static  let  shareInstance: SQLiteManager  = SQLiteManager ()override  init () {         super .init ()               guard  let  path = NSSearchPathForDirectoriesInDomains (.CachesDirectory , NSSearchPathDomainMask .UserDomainMask , true ).last else  {         return      }                    guard  let  cFilePath = path.stringByAppendingString("/demo.sqlite" ).cStringUsingEncoding(NSUTF8StringEncoding ) else  {         return      }                                   if  sqlite3_open(cFilePath, &db) != SQLITE_OK  {         return      }               createTable() } 
init()方法主要用来创建数据库。
建表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func  createTable ()          let  sql = "CREATE TABLE IF NOT EXISTS T_Student3(id INTEGER PRIMARY KEY AUTOINCREMENT,name TEXT,age INTEGER);"      guard  let  cSql = sql.cStringUsingEncoding(NSUTF8StringEncoding ) else  {         return      }                    if  sqlite3_exec(db, cSql, nil , nil ,  nil ) != SQLITE_OK  {         print ("创建表失败" )     }          print ("创建表成功" ) } 
执行 SQL 语句 版本一:
1 2 3 4 5 6 7 8 9 10 11 12 func  execSQL (sql: String) Bool  {         guard  let  cSql = sql.cStringUsingEncoding(NSUTF8StringEncoding ) else  {         return  false      }          if  sqlite3_exec(db, cSql, nil , nil , nil ) != SQLITE_OK  {         return  false      }          return  true  } 
上述写法存在弊端,无法实现参数可变。这对扩展性来说是致命的。FMDB 内部使用的是可变参数实现 SQL 语句的绑定。并且根据不同的类型,绑定不同类型的数据。
首先,关于什么是可变参数请左转Swift 基础知识点中的函数篇 
所以考虑到我们可以传入不同类型,不同个数的参数,而依然不用改写代码,因此应该使用如下方法进行数据库操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 let  SQLITE_TRANSIENT  = unsafeBitCast (-1 , sqlite3_destructor_type.self )func  execSQL (sql: String, args: CVarArgType...) Bool  {         guard  let  cSql = sql.cStringUsingEncoding(NSUTF8StringEncoding ) else  {         return  false      }          var  statement: COpaquePointer  = nil                     if  sqlite3_prepare_v2(db, cSql, -1 , &statement, nil ) != SQLITE_OK  {         return  false      }                    var  index: Int32  = 1                for  arg in  args {                           if  arg is  Int  {                          let  temp = Int32 (arg as ! Int )                          sqlite3_bind_int(statement, index, temp)         } else  if  arg is  Double  {             let  temp = arg as ! Double              sqlite3_bind_double(statement, index, temp)         } else  if  arg is  String  {             let  temp = arg as ! String              guard  let  cTemp = temp.cStringUsingEncoding(NSUTF8StringEncoding ) else  {                 continue              }                                                                              sqlite3_bind_text(statement, index, cTemp, -1 , SQLITE_TRANSIENT )         } else  {             print ("二进制" )         }                  index += 1      }               if  sqlite3_step(statement) != SQLITE_DONE  {         return  false      }               sqlite3_finalize(statement)     return  true  } 
知识点:1 anyObject CVarArgType 表示 anyObject? 表示任一类型,这里其实用 any 或者 anyObject 都可以。
any 和 anyObject 的区别:
 
在 OC 中,编译器不会对声明为 id 的变量进行类型检查,它可以表示任意类的实例。这时 OC 动态特性的体现。
但是 Swift 中编译器不仅不会对 AnyObject 实例的方法调用做出检查,还会在所有的 AnyObject 方法调用中返回 Optional 的结果。所以在 Swift 环境下使用起来非常麻烦,还必须先确定 AnyObject 真正的类型并进行转换以后再进行调用。
原来某个 API 返回的是一个 id,在 Swift 中就将被映射为 AnyObject? (因为 id 是可以指向 nil 的,所以需要一个 Optional)
any 则可以代表所有类型,包括 struct 和 enum 
知识点2:注意 Swift 中的字符串绑定 由于 Swift 中没有宏这个概念,因此无法打出 OC 中的宏。在 stackoverflow 上,寻得解决方案:
1 let  SQLITE_TRANSIENT  = unsafeBitCast (-1 , sqlite3_destructor_type.self )
sqlite3_bind_text(statement, index, cTemp, -1, SQLITE_TRANSIENT)
在我们要保存的类中就可以直接使用封装的方法:
1 2 3 4 5 6 7 8 9 10 11 12 func  insertStudent ()               let  manager = SQLiteManager .shareInstance               let  sql = "INSERT INTO T_Student3(name, age)VALUES(?, ?);"                manager.execSQL(sql, args: name, age) } 
但是上述方法也有弊端,就是当插入大批量的数据时,耗时非常久。大概 10000 条数据 耗时在5秒或者6秒左右。所以我们要优化代码。 耗时的原因是:每次执行 SQL 语句,系统就会自动开启事务,当 SQL 语句执行完毕,系统就会自动提交事务并关闭事务。因此,在执行插入多条数据时,我们只需进行一次打开,提交并关闭一次事务。
只要我们手动开启了事务,系统就不会再自动帮我们开启事务了,只要我们自己提交了事务,系统就不会再自动帮我们提交事务了。
因此,我们需要自己开启事务并提交和关闭事务。
首先,需要在 SQLManager 类中加入开启事务、关闭事务、回滚事务的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 func  beginTransaction ()     let  sql = "BEGIN TRANSACTION;" ;     execSQL(sql) } func  commitTransaction ()     let  sql = "COMMIT TRANSACTION;" ;     execSQL(sql) } func  rollbackTransaction ()     let  sql = "ROLLBACK TRANSACTION;" ;     execSQL(sql) } 
其次,在每次插入多条数据之前,需要手动调用该方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 override  func  touchesBegan (touches: Set<UITouch>, withEvent event: UIEvent?)          let  manager = SQLiteManager .shareInstance               let  start = CFAbsoluteTimeGetCurrent ()               manager.beginTransaction()          for  i in  0 ..<10000  {         let  stu = Student (name: "mm + \(i)" , age: 20  + i)         stu.insertStudent()     }          let  end = CFAbsoluteTimeGetCurrent ()          manager.commitTransaction()          print ("耗时\(end - start)秒" ) } 
前后对比,使用手动事务提交和不使用手动事务提交的差距:
1 2 创建表成功 耗时7.7067089676857秒 
1 2 创建表成功 耗时0.114471018314362秒 
可以看到性能提高了很多倍。
对于事务这个概念,其实是保证线程安全的。
rollBack 是在中途数据可能出现问题的地方,插入
1 2 3 4 5 6 7 func  rollbackTransaction ()    let  sql = "ROLLBACK TRANSACTION;" ;    execSQL(sql) } 
则,如果真的出现了问题,数据就会回到最初未执行 SQL 语句的地方。
查询 查询语句与其他的 SQL 语句执行时有不同。
首先,与执行 SQL 语句,如果执行成功即返回 SQLITE_OK。则会将句柄给到我们。
具体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 func  execQuery (sql: String) String : AnyObject ]]? {         guard  let  cSql = sql.cStringUsingEncoding(NSUTF8StringEncoding ) else  {         return  nil      }               var  statement: COpaquePointer  = nil                if  sqlite3_prepare_v2(db, cSql, -1 , &statement, nil ) != SQLITE_OK  {         return  nil      }               var  dicts = [[String : AnyObject ]]()               while  sqlite3_step(statement) == SQLITE_ROW  {                  var  record = [String : AnyObject ]()                           let  count  = sqlite3_column_count(statement)                           for  i in  0 ..<count  {                          let  cName = sqlite3_column_name(statement, i)             guard  let  name = String (CString : cName, encoding: NSUTF8StringEncoding ) else  {                 continue              }                                       let  type = sqlite3_column_type(statement, i)                                       switch  type {             case  SQLITE_INTEGER :                 let  value = Int (sqlite3_column_int(statement, i))                 record[name] = value             case  SQLITE_FLOAT :                 let  value = sqlite3_column_double(statement, i)                 record[name] = value             case  SQLITE3_TEXT :                                  let  cStr = UnsafePointer <Int8 >(sqlite3_column_text(statement, i))                 let  str = String (CString : cStr, encoding: NSUTF8StringEncoding )                 record[name] = str             default :                 print ("二进制" )             }         }                  dicts.append(record)     }     return  dicts } 
利用句柄取出一条数据:
1 sqlite3_step(statement) == SQLITE_ROW  
首先通过句柄来读取出当前一共有多少列,几多少个字段。
1 let  count  = sqlite3_column_count(statement)
然后遍历所有字段,取出每个字段的名称(这里需要注意返回值类型):
1 let  cName = sqlite3_column_name(statement, i)
cName 返回值类型为:UnsafePointer<Int8>
这里其实 cName 是一个指向 Int8 类型的指针。也就是 c 字符串,存储时需要转成 String 类型。
取出当前列(字段)类型:
1 let  type = sqlite3_column_type(statement, i)
判断当前列(字段)类型
FMDB FMDB 是 iOS 平台的 SQLite 数据库框架
FMDB 使用起来面向对象。提供了多线程安全的数据库操作。
主要有三个主要的类:FMDatabase 
一个 FMDatabase 对象代表了一个单独的 SQLite 数据库,用来执行数据库语句 
 
FMResultSet
FMDatabaseQueue
使用方式:Swift 中推荐使用 cocoapods 集成
导入 FMDB 代码到工程中 
创建桥接文件,导入 sqlite3.h 导入 FMDB.h 
将 Swift extensions 文件内容导入 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 var  db: FMDatabase! static  let  shareInstance = SQLiteManager ()override  init () {    super .init ()          guard  let  path = NSSearchPathForDirectoriesInDomains (.CachesDirectory , NSSearchPathDomainMask .UserDomainMask , true ).last else  {         return      }          let  filePath = (path as  NSString ).stringByAppendingPathComponent("demo.sqlite" )          db = FMDatabase (path: filePath)          if  db.open () == false  {         return      }          let  sql = "CREATE TABLE IF NOT EXISTS T_Student3(id INTEGER PRIMARY KEY AUTOINCREMENT,name TEXT,age INTEGER);"           db.executeUpdate(sql, withArgumentsInArray: nil ) } 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func  insertStudent ()     let  sql = "INSERT INTO T_Student3(name, age)VALUES(?, ?);"           SQLiteManager .shareInstance.db.executeUpdate(sql, withArgumentsInArray: [name, age]) } class  func  loadStudent ()          let  sql = "SELECT * FROM T_Student3;"           let  resultSet = SQLiteManager .shareInstance.db.executeQuery(sql, withArgumentsInArray: nil )          while  resultSet.next() {         let  id = resultSet.intForColumn("id" )         let  name = resultSet.stringForColumn("name" )         let  age = resultSet.intForColumn("age" )                  print ("id = \(id), name = \(name), age = \(age)" )     } }