I'm looking for a way to access an SQLite database in my app with Swift code.
I know that I can use an SQLite Wrapper in Objective C and use the bridging header, but I'd rather be able to do this project entirely in Swift. Is there a way to do this, if so, can someone point me to a reference that shows how to submit a query, retrieve rows, etc?
While you should probably use one of the many SQLite wrappers, if you wanted to know how to call the SQLite library yourself, you would:
Configure your Swift project to handle SQLite C calls. If using Xcode 9 or later, you can simply do:
import SQLite3
Create/open database.
let fileURL = try! FileManager.default
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("test.sqlite")
// open database
var db: OpaquePointer?
guard sqlite3_open(fileURL.path, &db) == SQLITE_OK else {
print("error opening database")
sqlite3_close(db)
db = nil
return
}
Note, I know it seems weird to close the database upon failure to open, but the sqlite3_open documentation makes it explicit that we must do so to avoid leaking memory:
Whether or not an error occurs when it is opened, resources associated with the database connection handle should be released by passing it to sqlite3_close() when it is no longer required.
Use sqlite3_exec to perform SQL (e.g. create table).
if sqlite3_exec(db, "create table if not exists test (id integer primary key autoincrement, name text)", nil, nil, nil) != SQLITE_OK {
let errmsg = String(cString: sqlite3_errmsg(db)!)
print("error creating table: \(errmsg)")
}
Use sqlite3_prepare_v2 to prepare SQL with ? placeholder to which we'll bind value.
var statement: OpaquePointer?
if sqlite3_prepare_v2(db, "insert into test (name) values (?)", -1, &statement, nil) != SQLITE_OK {
let errmsg = String(cString: sqlite3_errmsg(db)!)
print("error preparing insert: \(errmsg)")
}
if sqlite3_bind_text(statement, 1, "foo", -1, SQLITE_TRANSIENT) != SQLITE_OK {
let errmsg = String(cString: sqlite3_errmsg(db)!)
print("failure binding foo: \(errmsg)")
}
if sqlite3_step(statement) != SQLITE_DONE {
let errmsg = String(cString: sqlite3_errmsg(db)!)
print("failure inserting foo: \(errmsg)")
}
Note, that uses the SQLITE_TRANSIENT constant which can be implemented as follows:
internal let SQLITE_STATIC = unsafeBitCast(0, to: sqlite3_destructor_type.self)
internal let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
Reset SQL to insert another value. In this example, I'll insert a NULL value:
if sqlite3_reset(statement) != SQLITE_OK {
let errmsg = String(cString: sqlite3_errmsg(db)!)
print("error resetting prepared statement: \(errmsg)")
}
if sqlite3_bind_null(statement, 1) != SQLITE_OK {
let errmsg = String(cString: sqlite3_errmsg(db)!)
print("failure binding null: \(errmsg)")
}
if sqlite3_step(statement) != SQLITE_DONE {
let errmsg = String(cString: sqlite3_errmsg(db)!)
print("failure inserting null: \(errmsg)")
}
Finalize prepared statement to recover memory associated with that prepared statement:
if sqlite3_finalize(statement) != SQLITE_OK {
let errmsg = String(cString: sqlite3_errmsg(db)!)
print("error finalizing prepared statement: \(errmsg)")
}
statement = nil
Prepare new statement for selecting values from table and loop through retrieving the values:
if sqlite3_prepare_v2(db, "select id, name from test", -1, &statement, nil) != SQLITE_OK {
let errmsg = String(cString: sqlite3_errmsg(db)!)
print("error preparing select: \(errmsg)")
}
while sqlite3_step(statement) == SQLITE_ROW {
let id = sqlite3_column_int64(statement, 0)
print("id = \(id); ", terminator: "")
if let cString = sqlite3_column_text(statement, 1) {
let name = String(cString: cString)
print("name = \(name)")
} else {
print("name not found")
}
}
if sqlite3_finalize(statement) != SQLITE_OK {
let errmsg = String(cString: sqlite3_errmsg(db)!)
print("error finalizing prepared statement: \(errmsg)")
}
statement = nil
Close database:
if sqlite3_close(db) != SQLITE_OK {
print("error closing database")
}
db = nil
For Swift 2 and older versions of Xcode, see previous revisions of this answer.
The best you can do is import the dynamic library inside a bridging header:
Add libsqlite3.dylib to your "Link Binary With Libraries" build phase
Create a "Bridging-Header.h" and add #import <sqlite3.h> to the top
set "Bridging-Header.h" for the "Objective-C Bridging Header" setting in Build Settings under "Swift Compiler - Code Generation"
You will then be able to access all of the c methods like sqlite3_open from your swift code.
However, you may just want to use FMDB and import that through the bridging header as that is a more object oriented wrapper of sqlite. Dealing with C pointers and structs will be cumbersome in Swift.
I too was looking for some way to interact with SQLite the same way I was used to doing previously in Objective-C. Admittedly, because of C compatibility, I just used the straight C API.
As no wrapper currently exists for SQLite in Swift and the SQLiteDB code mentioned above goes a bit higher level and assumes certain usage, I decided to create a wrapper and get a bit familiar with Swift in the process. You can find it here: https://github.com/chrismsimpson/SwiftSQLite.
var db = SQLiteDatabase();
db.open("/path/to/database.sqlite");
var statement = SQLiteStatement(database: db);
if ( statement.prepare("SELECT * FROM tableName WHERE Id = ?") != .Ok )
{
/* handle error */
}
statement.bindInt(1, value: 123);
if ( statement.step() == .Row )
{
/* do something with statement */
var id:Int = statement.getIntAt(0)
var stringValue:String? = statement.getStringAt(1)
var boolValue:Bool = statement.getBoolAt(2)
var dateValue:NSDate? = statement.getDateAt(3)
}
statement.finalizeStatement(); /* not called finalize() due to destructor/language keyword */
I've created an elegant SQLite library written completely in Swift called SwiftData.
Some of its feature are:
Bind objects conveniently to the string of SQL
Support for transactions and savepoints
Inline error handling
Completely thread safe by default
It provides an easy way to execute 'changes' (e.g. INSERT, UPDATE, DELETE, etc.):
if let err = SD.executeChange("INSERT INTO Cities (Name, Population, IsWarm, FoundedIn) VALUES ('Toronto', 2615060, 0, '1793-08-27')") {
//there was an error during the insert, handle it here
} else {
//no error, the row was inserted successfully
}
and 'queries' (e.g. SELECT):
let (resultSet, err) = SD.executeQuery("SELECT * FROM Cities")
if err != nil {
//there was an error during the query, handle it here
} else {
for row in resultSet {
if let name = row["Name"].asString() {
println("The City name is: \(name)")
}
if let population = row["Population"].asInt() {
println("The population is: \(population)")
}
if let isWarm = row["IsWarm"].asBool() {
if isWarm {
println("The city is warm")
} else {
println("The city is cold")
}
}
if let foundedIn = row["FoundedIn"].asDate() {
println("The city was founded in: \(foundedIn)")
}
}
}
Along with many more features!
You can check it out here
Yet another SQLite wrapper for Swift 2 and Swift 3: http://github.com/groue/GRDB.swift
Features:
An API that will look familiar to users of ccgus/fmdb
A low-level SQLite API that leverages the Swift standard library
A pretty Swift query interface for SQL-allergic developers
Support for the SQLite WAL mode, and concurrent database access for extra performance
A Record class that wraps result sets, eats your custom SQL queries for breakfast, and provides basic CRUD operations
Swift type freedom: pick the right Swift type that fits your data. Use Int64 when needed, or stick with the convenient Int. Store and read NSDate or NSDateComponents. Declare Swift enums for discrete data types. Define your own database-convertible types.
Database Migrations
Speed: https://github.com/groue/GRDB.swift/wiki/Performance
AppDelegate.swift
func createDatabase()
{
var path:Array=NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
let directory:String=path[0]
let DBpath=(directory as NSString).appendingPathComponent("Food.sqlite")
print(DBpath)
if (FileManager.default.fileExists(atPath: DBpath))
{
print("Successfull database create")
}
else
{
let pathfrom:String=(Bundle.main.resourcePath! as NSString).appendingPathComponent("Food.sqlite")
var success:Bool
do {
try FileManager.default.copyItem(atPath: pathfrom, toPath: DBpath)
success = true
} catch _ {
success = false
}
if !success
{
print("database not create ")
}
else
{
print("Successfull database new create")
}
}
}
Database.swift
import UIKit
class database: NSObject
{
func databasePath() -> NSString
{
var path:Array=NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
let directory:String=path[0]
let DBpath=(directory as NSString).appendingPathComponent("Food.sqlite")
if (FileManager.default.fileExists(atPath: DBpath))
{
return DBpath as NSString
}
return DBpath as NSString
}
func ExecuteQuery(_ str:String) -> Bool
{
var result:Bool=false
let DBpath:String=self.databasePath() as String
var db: OpaquePointer? = nil
var stmt:OpaquePointer? = nil
let strExec=str.cString(using: String.Encoding.utf8)
if (sqlite3_open(DBpath, &db)==SQLITE_OK)
{
if (sqlite3_prepare_v2(db, strExec! , -1, &stmt, nil) == SQLITE_OK)
{
if (sqlite3_step(stmt) == SQLITE_DONE)
{
result=true
}
}
sqlite3_finalize(stmt)
}
sqlite3_close(db)
return result
}
func SelectQuery(_ str:String) -> Array<Dictionary<String,String>>
{
var result:Array<Dictionary<String,String>>=[]
let DBpath:String=self.databasePath() as String
var db: OpaquePointer? = nil
var stmt:OpaquePointer? = nil
let strExec=str.cString(using: String.Encoding.utf8)
if ( sqlite3_open(DBpath,&db) == SQLITE_OK)
{
if (sqlite3_prepare_v2(db, strExec! , -1, &stmt, nil) == SQLITE_OK)
{
while (sqlite3_step(stmt) == SQLITE_ROW)
{
var i:Int32=0
let icount:Int32=sqlite3_column_count(stmt)
var dict=Dictionary<String, String>()
while i < icount
{
let strF=sqlite3_column_name(stmt, i)
let strV = sqlite3_column_text(stmt, i)
let rFiled:String=String(cString: strF!)
let rValue:String=String(cString: strV!)
//let rValue=String(cString: UnsafePointer<Int8>(strV!))
dict[rFiled] = rValue
i += 1
}
result.insert(dict, at: result.count)
}
sqlite3_finalize(stmt)
}
sqlite3_close(db)
}
return result
}
func AllSelectQuery(_ str:String) -> Array<Model>
{
var result:Array<Model>=[]
let DBpath:String=self.databasePath() as String
var db: OpaquePointer? = nil
var stmt:OpaquePointer? = nil
let strExec=str.cString(using: String.Encoding.utf8)
if ( sqlite3_open(DBpath,&db) == SQLITE_OK)
{
if (sqlite3_prepare_v2(db, strExec! , -1, &stmt, nil) == SQLITE_OK)
{
while (sqlite3_step(stmt) == SQLITE_ROW)
{
let mod=Model()
mod.id=String(cString: sqlite3_column_text(stmt, 0))
mod.image=String(cString: sqlite3_column_text(stmt, 1))
mod.name=String(cString: sqlite3_column_text(stmt, 2))
mod.foodtype=String(cString: sqlite3_column_text(stmt, 3))
mod.vegtype=String(cString: sqlite3_column_text(stmt, 4))
mod.details=String(cString: sqlite3_column_text(stmt, 5))
result.insert(mod, at: result.count)
}
sqlite3_finalize(stmt)
}
sqlite3_close(db)
}
return result
}
}
Model.swift
import UIKit
class Model: NSObject
{
var uid:Int = 0
var id:String = ""
var image:String = ""
var name:String = ""
var foodtype:String = ""
var vegtype:String = ""
var details:String = ""
var mealtype:String = ""
var date:String = ""
}
Access database :
let DB=database()
var mod=Model()
database Query fire :
var DailyResult:Array<Model> = DB.AllSelectQuery("select * from food where foodtype == 'Sea Food' ORDER BY name ASC")
This is by far the best SQLite library that I've used in Swift: https://github.com/stephencelis/SQLite.swift
Look at the code examples. So much cleaner than the C API:
import SQLite
let db = try Connection("path/to/db.sqlite3")
let users = Table("users")
let id = Expression<Int64>("id")
let name = Expression<String?>("name")
let email = Expression<String>("email")
try db.run(users.create { t in
t.column(id, primaryKey: true)
t.column(name)
t.column(email, unique: true)
})
// CREATE TABLE "users" (
// "id" INTEGER PRIMARY KEY NOT NULL,
// "name" TEXT,
// "email" TEXT NOT NULL UNIQUE
// )
let insert = users.insert(name <- "Alice", email <- "alice#mac.com")
let rowid = try db.run(insert)
// INSERT INTO "users" ("name", "email") VALUES ('Alice', 'alice#mac.com')
for user in try db.prepare(users) {
print("id: \(user[id]), name: \(user[name]), email: \(user[email])")
// id: 1, name: Optional("Alice"), email: alice#mac.com
}
// SELECT * FROM "users"
let alice = users.filter(id == rowid)
try db.run(alice.update(email <- email.replace("mac.com", with: "me.com")))
// UPDATE "users" SET "email" = replace("email", 'mac.com', 'me.com')
// WHERE ("id" = 1)
try db.run(alice.delete())
// DELETE FROM "users" WHERE ("id" = 1)
try db.scalar(users.count) // 0
// SELECT count(*) FROM "users"
The documentation also says that "SQLite.swift also works as a lightweight, Swift-friendly wrapper over the C API," and follows with some examples of that.
You can use this library in Swift for SQLite
https://github.com/pmurphyjam/SQLiteDemo
SQLiteDemo
SQLite Demo using Swift with SQLDataAccess class written in Swift
Adding to Your Project
You only need three files to add to your project
* SQLDataAccess.swift
* DataConstants.swift
* Bridging-Header.h
Bridging-Header must be set in your Xcode's project 'Objective-C Bridging Header' under 'Swift Compiler - General'
Examples for Use
Just follow the code in ViewController.swift to see how to write simple SQL with SQLDataAccess.swift
First you need to open the SQLite Database your dealing with
let db = SQLDataAccess.shared
db.setDBName(name:"SQLite.db")
let opened = db.openConnection(copyFile:true)
If openConnection succeeded, now you can do a simple insert into Table AppInfo
//Insert into Table AppInfo
let status = db.executeStatement("insert into AppInfo (name,value,descrip,date) values(?,?,?,?)",
”SQLiteDemo","1.0.2","unencrypted",Date())
if(status)
{
//Read Table AppInfo into an Array of Dictionaries
let results = db.getRecordsForQuery("select * from AppInfo ")
NSLog("Results = \(results)")
}
See how simple that was!
The first term in db.executeStatement is your SQL as String, all the terms that follow are a variadic argument list of type Any, and are your parameters in an Array. All these terms are separated by commas in your list of SQL arguments. You can enter Strings, Integers, Date’s, and Blobs right after the sequel statement since all of these terms are considered to be parameters for the sequel. The variadic argument array just makes it convenient to enter all your sequel in just one executeStatement or getRecordsForQuery call. If you don’t have any parameters, don’t enter anything after your SQL.
The results array is an Array of Dictionary’s where the ‘key’ is your tables column name, and the ‘value’ is your data obtained from SQLite. You can easily iterate through this array with a for loop or print it out directly or assign these Dictionary elements to custom data object Classes that you use in your View Controllers for model consumption.
for dic in results as! [[String:AnyObject]] {
print(“result = \(dic)”)
}
SQLDataAccess will store, text, double, float, blob, Date, integer and long long integers.
For Blobs you can store binary, varbinary, blob.
For Text you can store char, character, clob, national varying character, native character, nchar, nvarchar, varchar, variant, varying character, text.
For Dates you can store datetime, time, timestamp, date.
For Integers you can store bigint, bit, bool, boolean, int2, int8, integer, mediumint, smallint, tinyint, int.
For Doubles you can store decimal, double precision, float, numeric, real, double. Double has the most precision.
You can even store Nulls of type Null.
In ViewController.swift a more complex example is done showing how to insert a Dictionary as a 'Blob'. In addition SQLDataAccess
understands native Swift Date() so you can insert these objects with out converting, and it will convert them to text and store them,
and when retrieved convert them back from text to Date.
Of course the real power of SQLite is it's Transaction capability. Here you can literally queue up 400 SQL statements with parameters
and insert them all at once which is really powerful since it's so fast. ViewController.swift also shows you an example of how to do this.
All you're really doing is creating an Array of Dictionaries called 'sqlAndParams', in this Array your storing Dictionaries with two keys
'SQL' for the String sequel statement or query, and 'PARAMS' which is just an Array of native objects SQLite understands for that query.
Each 'sqlParams' which is an individual Dictionary of sequel query plus parameters is then stored in the 'sqlAndParams' Array.
Once you've created this array, you just call.
let status = db.executeTransaction(sqlAndParams)
if(status)
{
//Read Table AppInfo into an Array of Dictionaries for the above Transactions
let results = db.getRecordsForQuery("select * from AppInfo ")
NSLog("Results = \(results)")
}
In addition all executeStatement and getRecordsForQuery methods can be done with simple String for SQL query and an Array for the parameters needed by the query.
let sql : String = "insert into AppInfo (name,value,descrip) values(?,?,?)"
let params : Array = ["SQLiteDemo","1.0.0","unencrypted"]
let status = db.executeStatement(sql, withParameters: params)
if(status)
{
//Read Table AppInfo into an Array of Dictionaries for the above Transactions
let results = db.getRecordsForQuery("select * from AppInfo ")
NSLog("Results = \(results)")
}
An Objective-C version also exists and is called the same SQLDataAccess, so now you can choose to write your sequel in Objective-C or Swift.
In addition SQLDataAccess will also work with SQLCipher, the present code isn't setup yet to work with it, but it's pretty easy to do, and
an example of how to do this is actually in the Objective-C version of SQLDataAccess.
SQLDataAccess is a very fast and efficient class, and can be used in place of CoreData which really just uses SQLite as it's underlying data
store without all the CoreData core data integrity fault crashes that come with CoreData.
I have written a SQLite3 wrapper library written in Swift.
This is actually a very high level wrapper with very simple API, but anyway, it has low-level C inter-op code, and I post here a (simplified) part of it to shows the C inter-op.
struct C
{
static let NULL = COpaquePointer.null()
}
func open(filename:String, flags:OpenFlag)
{
let name2 = filename.cStringUsingEncoding(NSUTF8StringEncoding)!
let r = sqlite3_open_v2(name2, &_rawptr, flags.value, UnsafePointer<Int8>.null())
checkNoErrorWith(resultCode: r)
}
func close()
{
let r = sqlite3_close(_rawptr)
checkNoErrorWith(resultCode: r)
_rawptr = C.NULL
}
func prepare(SQL:String) -> (statements:[Core.Statement], tail:String)
{
func once(zSql:UnsafePointer<Int8>, len:Int32, inout zTail:UnsafePointer<Int8>) -> Core.Statement?
{
var pStmt = C.NULL
let r = sqlite3_prepare_v2(_rawptr, zSql, len, &pStmt, &zTail)
checkNoErrorWith(resultCode: r)
if pStmt == C.NULL
{
return nil
}
return Core.Statement(database: self, pointerToRawCStatementObject: pStmt)
}
var stmts:[Core.Statement] = []
let sql2 = SQL as NSString
var zSql = UnsafePointer<Int8>(sql2.UTF8String)
var zTail = UnsafePointer<Int8>.null()
var len1 = sql2.lengthOfBytesUsingEncoding(NSUTF8StringEncoding);
var maxlen2 = Int32(len1)+1
while let one = once(zSql, maxlen2, &zTail)
{
stmts.append(one)
zSql = zTail
}
let rest1 = String.fromCString(zTail)
let rest2 = rest1 == nil ? "" : rest1!
return (stmts, rest2)
}
func step() -> Bool
{
let rc1 = sqlite3_step(_rawptr)
switch rc1
{
case SQLITE_ROW:
return true
case SQLITE_DONE:
return false
default:
database.checkNoErrorWith(resultCode: rc1)
}
}
func columnText(at index:Int32) -> String
{
let bc = sqlite3_column_bytes(_rawptr, Int32(index))
let cs = sqlite3_column_text(_rawptr, Int32(index))
let s1 = bc == 0 ? "" : String.fromCString(UnsafePointer<CChar>(cs))!
return s1
}
func finalize()
{
let r = sqlite3_finalize(_rawptr)
database.checkNoErrorWith(resultCode: r)
_rawptr = C.NULL
}
If you want a full source code of this low level wrapper, see these files.
https://github.com/Eonil/SQLite3/blob/master/Swift/Sources/Core.Statement.swift
https://github.com/Eonil/SQLite3/blob/master/Swift/Sources/Core.Database.swift
Configure your Swift project to handle SQLite C calls:
Create bridging header file to the project. See the Importing Objective-C into Swift section of the Using Swift with Cocoa and Objective-C. This bridging header should import sqlite3.h:
Add the libsqlite3.0.dylib to your project. See Apple's documentation regarding adding library/framework to one's project.
and used following code
func executeQuery(query: NSString ) -> Int
{
if sqlite3_open(databasePath! as String, &database) != SQLITE_OK
{
println("Databse is not open")
return 0
}
else
{
query.stringByReplacingOccurrencesOfString("null", withString: "")
var cStatement:COpaquePointer = nil
var executeSql = query as NSString
var lastId : Int?
var sqlStatement = executeSql.cStringUsingEncoding(NSUTF8StringEncoding)
sqlite3_prepare_v2(database, sqlStatement, -1, &cStatement, nil)
var execute = sqlite3_step(cStatement)
println("\(execute)")
if execute == SQLITE_DONE
{
lastId = Int(sqlite3_last_insert_rowid(database))
}
else
{
println("Error in Run Statement :- \(sqlite3_errmsg16(database))")
}
sqlite3_finalize(cStatement)
return lastId!
}
}
func ViewAllData(query: NSString, error: NSError) -> NSArray
{
var cStatement = COpaquePointer()
var result : AnyObject = NSNull()
var thisArray : NSMutableArray = NSMutableArray(capacity: 4)
cStatement = prepare(query)
if cStatement != nil
{
while sqlite3_step(cStatement) == SQLITE_ROW
{
result = NSNull()
var thisDict : NSMutableDictionary = NSMutableDictionary(capacity: 4)
for var i = 0 ; i < Int(sqlite3_column_count(cStatement)) ; i++
{
if sqlite3_column_type(cStatement, Int32(i)) == 0
{
continue
}
if sqlite3_column_decltype(cStatement, Int32(i)) != nil && strcasecmp(sqlite3_column_decltype(cStatement, Int32(i)), "Boolean") == 0
{
var temp = sqlite3_column_int(cStatement, Int32(i))
if temp == 0
{
result = NSNumber(bool : false)
}
else
{
result = NSNumber(bool : true)
}
}
else if sqlite3_column_type(cStatement,Int32(i)) == SQLITE_INTEGER
{
var temp = sqlite3_column_int(cStatement,Int32(i))
result = NSNumber(int : temp)
}
else if sqlite3_column_type(cStatement,Int32(i)) == SQLITE_FLOAT
{
var temp = sqlite3_column_double(cStatement,Int32(i))
result = NSNumber(double: temp)
}
else
{
if sqlite3_column_text(cStatement, Int32(i)) != nil
{
var temp = sqlite3_column_text(cStatement,Int32(i))
result = String.fromCString(UnsafePointer<CChar>(temp))!
var keyString = sqlite3_column_name(cStatement,Int32(i))
thisDict.setObject(result, forKey: String.fromCString(UnsafePointer<CChar>(keyString))!)
}
result = NSNull()
}
if result as! NSObject != NSNull()
{
var keyString = sqlite3_column_name(cStatement,Int32(i))
thisDict.setObject(result, forKey: String.fromCString(UnsafePointer<CChar>(keyString))!)
}
}
thisArray.addObject(NSMutableDictionary(dictionary: thisDict))
}
sqlite3_finalize(cStatement)
}
return thisArray
}
func prepare(sql : NSString) -> COpaquePointer
{
var cStatement:COpaquePointer = nil
sqlite3_open(databasePath! as String, &database)
var utfSql = sql.UTF8String
if sqlite3_prepare(database, utfSql, -1, &cStatement, nil) == 0
{
sqlite3_close(database)
return cStatement
}
else
{
sqlite3_close(database)
return nil
}
}
}
Sometimes, a Swift version of the "SQLite in 5 minutes or less" approach shown on sqlite.org is sufficient.
The "5 minutes or less" approach uses sqlite3_exec() which is a convenience wrapper for sqlite3_prepare(), sqlite3_step(), sqlite3_column(), and sqlite3_finalize().
Swift 2.2 can directly support the sqlite3_exec() callback function pointer as either a global, non-instance procedure func or a non-capturing literal closure {}.
Readable typealias
typealias sqlite3 = COpaquePointer
typealias CCharHandle = UnsafeMutablePointer<UnsafeMutablePointer<CChar>>
typealias CCharPointer = UnsafeMutablePointer<CChar>
typealias CVoidPointer = UnsafeMutablePointer<Void>
Callback Approach
func callback(
resultVoidPointer: CVoidPointer, // void *NotUsed
columnCount: CInt, // int argc
values: CCharHandle, // char **argv
columns: CCharHandle // char **azColName
) -> CInt {
for i in 0 ..< Int(columnCount) {
guard let value = String.fromCString(values[i])
else { continue }
guard let column = String.fromCString(columns[i])
else { continue }
print("\(column) = \(value)")
}
return 0 // status ok
}
func sqlQueryCallbackBasic(argc: Int, argv: [String]) -> Int {
var db: sqlite3 = nil
var zErrMsg:CCharPointer = nil
var rc: Int32 = 0 // result code
if argc != 3 {
print(String(format: "ERROR: Usage: %s DATABASE SQL-STATEMENT", argv[0]))
return 1
}
rc = sqlite3_open(argv[1], &db)
if rc != 0 {
print("ERROR: sqlite3_open " + String.fromCString(sqlite3_errmsg(db))! ?? "" )
sqlite3_close(db)
return 1
}
rc = sqlite3_exec(db, argv[2], callback, nil, &zErrMsg)
if rc != SQLITE_OK {
print("ERROR: sqlite3_exec " + String.fromCString(zErrMsg)! ?? "")
sqlite3_free(zErrMsg)
}
sqlite3_close(db)
return 0
}
Closure Approach
func sqlQueryClosureBasic(argc argc: Int, argv: [String]) -> Int {
var db: sqlite3 = nil
var zErrMsg:CCharPointer = nil
var rc: Int32 = 0
if argc != 3 {
print(String(format: "ERROR: Usage: %s DATABASE SQL-STATEMENT", argv[0]))
return 1
}
rc = sqlite3_open(argv[1], &db)
if rc != 0 {
print("ERROR: sqlite3_open " + String.fromCString(sqlite3_errmsg(db))! ?? "" )
sqlite3_close(db)
return 1
}
rc = sqlite3_exec(
db, // database
argv[2], // statement
{ // callback: non-capturing closure
resultVoidPointer, columnCount, values, columns in
for i in 0 ..< Int(columnCount) {
guard let value = String.fromCString(values[i])
else { continue }
guard let column = String.fromCString(columns[i])
else { continue }
print("\(column) = \(value)")
}
return 0
},
nil,
&zErrMsg
)
if rc != SQLITE_OK {
let errorMsg = String.fromCString(zErrMsg)! ?? ""
print("ERROR: sqlite3_exec \(errorMsg)")
sqlite3_free(zErrMsg)
}
sqlite3_close(db)
return 0
}
To prepare an Xcode project to call a C library such as SQLite, one needs to (1) add a Bridging-Header.h file reference C headers like #import "sqlite3.h", (2) add Bridging-Header.h to Objective-C Bridging Header in project settings, and (3) add libsqlite3.tbd to Link Binary With Library target settings.
The sqlite.org's "SQLite in 5 minutes or less" example is implemented in a Swift Xcode7 project here.
You can easlity configure SQLite with swift using single ton class as well.
Refer
https://github.com/hasyapanchasara/SQLite_SingleManagerClass
Method to create database
func methodToCreateDatabase() -> NSURL?{}
Method to insert, update and delete data
func methodToInsertUpdateDeleteData(strQuery : String) -> Bool{}
Method to select data
func methodToSelectData(strQuery : String) -> NSMutableArray{}
Related
I'm trying to serialise JSON values from a serde_json::Map into a SQLite database. I would like to use multiple data types in the Map and have them converted into the appropriate SQLite datatypes.
The map is created in collect_values and is passed to the write_record function. The new_db function creates a rustqlite::Connection context that is passed to write_record
However when I try to insert the values from the map, I get this error
error[E0277]: the trait bound `serde_json::value::Value: rusqlite::types::to_sql::ToSql` is not satisfied
--> src/main.rs:51:9
|
51 | / params![
52 | | &values.get("TEST-KEY-1").unwrap_or(&missing_value),
53 | | &values.get("TEST-KEY-2").unwrap_or(&missing_value)
54 | | ],
| |_________^ the trait `rusqlite::types::to_sql::ToSql` is not implemented for `serde_json::value::Value`
|
= note: required because of the requirements on the impl of `rusqlite::types::to_sql::ToSql` for `&serde_json::value::Value`
= note: required because of the requirements on the impl of `rusqlite::types::to_sql::ToSql` for `&&serde_json::value::Value`
= note: required for the cast to the object type `dyn rusqlite::types::to_sql::ToSql`
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
Do I need to implement the serialiser manually? I thought rusqlite's types module already has this done.
Cargo.toml
[package]
name = "sqlite-min-example"
version = "0.1.0"
authors = ["test"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rusqlite = "0.23.1"
serde_json = "1.0"
serde = {version = "1.0.113", default-features = false}
main.rs
use rusqlite::{params, Connection, Result};
use serde_json::json;
use serde_json::map::Map;
fn main() {
println!("Opening connection");
let conn = new_db();
match conn {
Ok(ctx) => {
let mp = collect_values();
let res = insert_record(&mp, &ctx);
}
Err(e) => {
eprintln!("{}", e);
}
}
}
pub fn new_db() -> Result<rusqlite::Connection> {
let conn = Connection::open("test.db")?;
//let conn = Connection::open_in_memory()?;
conn.execute(
"CREATE TABLE IF NOT EXISTS testdb (
field1 INTEGER,
field2 INTEGER
)",
params![],
)?;
// return the new connection context (object)
Ok(conn)
}
pub fn insert_record(
values: &serde_json::map::Map<std::string::String, serde_json::value::Value>,
conn: &rusqlite::Connection,
) -> Result<()> {
// Insert this if we can't find a value for a key for some reason...
let missing_value = json!("MISSINGVAL");
conn.execute(
"INSERT INTO testdb
(
field1,
field2
)
VALUES (
?1, ?2
)",
params![
&values.get("TEST-KEY-1").unwrap_or(&missing_value),
&values.get("TEST-KEY-2").unwrap_or(&missing_value)
],
)?;
// return any errors that occured
Ok(())
}
pub fn collect_values() -> serde_json::map::Map<std::string::String, serde_json::value::Value> {
// Take in the Modbus context and return a map of keys (field names) and their associated values
let mut map = Map::new();
map.insert("TEST-KEY-1".to_string(), json!(1234));
map.insert("TEST-KEY-2".to_string(), json!(5678));
return map;
}
Add the appropriate feature to rusqlite in your Cargo.toml:
rusqlite = { version = "0.23.1", features = ["serde_json"] }
See also:
How do you enable a Rust "crate feature"?
How to tell what "features" are available per crate?
i am writing an extension to decrypt my NSMutableData ,
extension NSMutableData {
func decrypt() -> NSData {
let decryptMethod = (User.sharedInstance.data?.encrypt_method)!
let key = User.sharedInstance.defaultKey()
return decrypt(methodNumber:decryptMethod , key:key )
}
func decrypt( methodNumber: Decrypter.DecryptType, key: String) -> NSData {
....
}
}
the decrypt method work is fine when i decrypt NSMutableData , but failed to decrypt Data even i cast it to NSMutableData
func xxx() -> Data {
Var encryptedData:Data = getEncData()
let dataToDecrypt = encryptedData as! NSMutableData
let data = dataToDecrypt.decrypt()
return data as Data
}
the code crash at
let data = dataToDecrypt.decrypt()
and tell me "unrecognized selector", here is the crash log
[OS_dispatch_data decrypt]: unrecognized selector sent to instance 0x157fbf0b0
seems my NSMutableData has been cast to OS_dispatch_data so cause "unrecognized selector" , what should i do to cast my Data to NSMutableData correctly ?
The key is: encryptedData.withUnsafeBytes {encryptedBytes in ... }.
Example from sunsetted documentation section:
AES encryption in CBC mode with a random IV (Swift 3+)
The iv is prefixed to the encrypted data
aesCBC128Encrypt will create a random IV and prefixed to the encrypted code.
aesCBC128Decrypt will use the prefixed IV during decryption.
Inputs are the data and key are Data objects. If an encoded form such as Base64 if required convert to and/or from in the calling method.
The key should be exactly 128-bits (16-bytes), 192-bits (24-bytes) or 256-bits (32-bytes) in length. If another key size is used an error will be thrown.
PKCS#7 padding is set by default.
This example requires Common Crypto
It is necessary to have a bridging header to the project:
#import <CommonCrypto/CommonCrypto.h>
Add the Security.framework to the project.
This is example, not production code.
enum AESError: Error {
case KeyError((String, Int))
case IVError((String, Int))
case CryptorError((String, Int))
}
// The iv is prefixed to the encrypted data
func aesCBCEncrypt(data:Data, keyData:Data) throws -> Data {
let keyLength = keyData.count
let validKeyLengths = [kCCKeySizeAES128, kCCKeySizeAES192, kCCKeySizeAES256]
if (validKeyLengths.contains(keyLength) == false) {
throw AESError.KeyError(("Invalid key length", keyLength))
}
let ivSize = kCCBlockSizeAES128;
let cryptLength = size_t(ivSize + data.count + kCCBlockSizeAES128)
var cryptData = Data(count:cryptLength)
let status = cryptData.withUnsafeMutableBytes {ivBytes in
SecRandomCopyBytes(kSecRandomDefault, kCCBlockSizeAES128, ivBytes)
}
if (status != 0) {
throw AESError.IVError(("IV generation failed", Int(status)))
}
var numBytesEncrypted :size_t = 0
let options = CCOptions(kCCOptionPKCS7Padding)
let cryptStatus = cryptData.withUnsafeMutableBytes {cryptBytes in
data.withUnsafeBytes {dataBytes in
keyData.withUnsafeBytes {keyBytes in
CCCrypt(CCOperation(kCCEncrypt),
CCAlgorithm(kCCAlgorithmAES),
options,
keyBytes, keyLength,
cryptBytes,
dataBytes, data.count,
cryptBytes+kCCBlockSizeAES128, cryptLength,
&numBytesEncrypted)
}
}
}
if UInt32(cryptStatus) == UInt32(kCCSuccess) {
cryptData.count = numBytesEncrypted + ivSize
}
else {
throw AESError.CryptorError(("Encryption failed", Int(cryptStatus)))
}
return cryptData;
}
// The iv is prefixed to the encrypted data
func aesCBCDecrypt(data:Data, keyData:Data) throws -> Data? {
let keyLength = keyData.count
let validKeyLengths = [kCCKeySizeAES128, kCCKeySizeAES192, kCCKeySizeAES256]
if (validKeyLengths.contains(keyLength) == false) {
throw AESError.KeyError(("Invalid key length", keyLength))
}
let ivSize = kCCBlockSizeAES128;
let clearLength = size_t(data.count - ivSize)
var clearData = Data(count:clearLength)
var numBytesDecrypted :size_t = 0
let options = CCOptions(kCCOptionPKCS7Padding)
let cryptStatus = clearData.withUnsafeMutableBytes {cryptBytes in
data.withUnsafeBytes {dataBytes in
keyData.withUnsafeBytes {keyBytes in
CCCrypt(CCOperation(kCCDecrypt),
CCAlgorithm(kCCAlgorithmAES128),
options,
keyBytes, keyLength,
dataBytes,
dataBytes+kCCBlockSizeAES128, clearLength,
cryptBytes, clearLength,
&numBytesDecrypted)
}
}
}
if UInt32(cryptStatus) == UInt32(kCCSuccess) {
clearData.count = numBytesDecrypted
}
else {
throw AESError.CryptorError(("Decryption failed", Int(cryptStatus)))
}
return clearData;
}
Example usage:
let clearData = "clearData0123456".data(using:String.Encoding.utf8)!
let keyData = "keyData890123456".data(using:String.Encoding.utf8)!
print("clearData: \(clearData as NSData)")
print("keyData: \(keyData as NSData)")
var cryptData :Data?
do {
cryptData = try aesCBCEncrypt(data:clearData, keyData:keyData)
print("cryptData: \(cryptData! as NSData)")
}
catch (let status) {
print("Error aesCBCEncrypt: \(status)")
}
let decryptData :Data?
do {
let decryptData = try aesCBCDecrypt(data:cryptData!, keyData:keyData)
print("decryptData: \(decryptData! as NSData)")
}
catch (let status) {
print("Error aesCBCDecrypt: \(status)")
}
Example Output:
clearData: <636c6561 72446174 61303132 33343536>
keyData: <6b657944 61746138 39303132 33343536>
cryptData: <92c57393 f454d959 5a4d158f 6e1cd3e7 77986ee9 b2970f49 2bafcf1a 8ee9d51a bde49c31 d7780256 71837a61 60fa4be0>
decryptData: <636c6561 72446174 61303132 33343536>
Notes:
One typical problem with CBC mode example code is that it leaves the creation and sharing of the random IV to the user. This example includes generation of the IV, prefixed the encrypted data and uses the prefixed IV during decryption. This frees the casual user from the details that are necessary for CBC mode.
For security the encrypted data also should have authentication, this example code does not provide that in order to be small and allow better interoperability for other platforms.
Also missing is key derivation of the key from a password, it is suggested that PBKDF2 be used is text passwords are used as keying material.
For robust production ready multi-platform encryption code see RNCryptor.
Apple documentation: withUnsafeBytes.
Declaration
func withUnsafeBytes<T, Result>(of arg: inout T, _ body: (UnsafeRawBufferPointer) throws -> Result) rethrows -> Result
Parameters
arg
An instance to temporarily access through a raw buffer pointer.
body
A closure that takes a raw buffer pointer to the bytes of arg as its sole argument. If the closure has a return value, that value is also used as the return value of the withUnsafeBytes(of:_:) function. The buffer pointer argument is valid only for the duration of the closure’s execution.
Discussion
The buffer pointer argument to the body closure provides a collection interface to the raw bytes of arg. The buffer is the size of the instance passed as arg and does not include any remote storage.
I´m trying to create a function that injects queries to SQLite in Swift 3 but I´m having problems with the escaping chars
class SQLiteQueryManager {
static func insertNewStore(StoreId: Int64, Name: String, Address: String) -> Bool {
let vCommand = "INSERT INTO Store (Id, StoreId, Name, Address) VALUES (\(SQLiteConnectionManager.nextID("Store")),\(StoreId),'\(Name)','\(Address)')"
return SQLiteConnectionManager.insertDatabase(vCommand)
}
}
The problem ins that when I´m trying to execute SQLiteConnectionManager.insertDatabase function the string that I´m sending to SQLite looks like this:
INSERT INTO Store (Id, StoreId, Name, Address) VALUES (1,1,\'Tienda
1\',\'Dirección 1\')
And SQLite is rejecting the query.
I have tried .replacingOccurrences(of: "\", with: "") but it dose not work.
I have tested in DB Browser the query and works
INSERT INTO Store (Id, StoreId, Name, Address) VALUES (1,1,'Tienda
1','Dirección 1')
How can I remove the \??
My SQLite function is this:
static func insertDatabase(_ pCommand: String) -> Bool
{
var vInsertStatement: OpaquePointer? = nil
let vDB: OpaquePointer = SQLiteConnectionManager.openDatabase()
var vReturn: Bool
if sqlite3_prepare_v2(vDB, pCommand.replacingOccurrences(of: "\\", with: ""), -1, &vInsertStatement, nil) == SQLITE_OK {
if sqlite3_step(vInsertStatement) == SQLITE_DONE {
print("insertDatabase() correct with statement \(pCommand)")
vReturn = true
} else {
print("insertDatabase() fail with statement \(pCommand)")
vReturn = false
}
} else {
print("insertDatabase() pCommand could not be prepared")
vReturn = false
}
sqlite3_finalize(vInsertStatement)
sqlite3_close(vDB)
return vReturn
}
The Open function return ok so my guess is the escaping char or something like that.
This is the print output of the function:
insertDatabase() fail with statement INSERT INTO Store (Id, StoreId,
Name, Address) VALUES (1,1,'Tienda 1','Dirección 1')
UPDATE 1:
sqlite3_step(vInsertStatement) is returning SQLITE_MISUSE but I can't find the mistake, the DB is in the bundle and the open() statement work and a select I'm doing works, what can be wrong?
UPDATE 2:
This is how I open de DB and returns OK:
private static func openDatabase() -> OpaquePointer {
var vDB: OpaquePointer? = nil
mDBURL = Bundle.main.url(forResource: "ARDB", withExtension: "db")
if let vDBURL = mDBURL{
if sqlite3_open(vDBURL.absoluteString, &vDB) == SQLITE_OK {
print("The database is open.")
} else {
print("Unable to open database in method openDatabase().")
}
return vDB!
} else {
return vDB!
}
}
Then I run this to get the last Id and works:
static func nextID(_ pTableName: String!) -> Int
{
var vGetIdStatement: OpaquePointer? = nil
let vDB: OpaquePointer = SQLiteConnectionManager.openDatabase()
let vCommand = String(format: "SELECT Id FROM %# ORDER BY Id DESC LIMIT 1", pTableName)
var vResult: Int32? = 0
if sqlite3_prepare_v2(vDB, vCommand, -1, &vGetIdStatement, nil) == SQLITE_OK {
if sqlite3_step(vGetIdStatement) == SQLITE_ROW {
vResult = sqlite3_column_int(vGetIdStatement, 0)
print("nextID() correct with statement \(vCommand)")
} else {
print("nextID() fail with statement \(vCommand)")
}
} else {
print("nextID() statement could not be prepared")
}
sqlite3_finalize(vGetIdStatement)
sqlite3_close(vDB)
var id: Int = 1
if (vResult != nil)
{
id = Int(vResult!) + 1
}
return id
}
I have change my insert function to this with or without cString statement:
static func insertDatabase(_ pCommand: String) -> Bool
{
var vInsertStatement: OpaquePointer? = nil
let vDB: OpaquePointer = SQLiteConnectionManager.openDatabase()
var vReturn: Bool
if sqlite3_prepare_v2(vDB, pCommand.cString(using: .utf8), -1, &vInsertStatement, nil) == SQLITE_OK {
if sqlite3_step(vInsertStatement) == SQLITE_DONE {
print("insertDatabase() correct with statement \(pCommand)")
vReturn = true
} else {
print("insertDatabase() fail with statement \(pCommand) with error: \(sqlite3_step(vInsertStatement)) : \(sqlite3_errmsg(vDB))")
vReturn = false
}
} else {
print("insertDatabase() \(pCommand) could not be prepared with error: \(sqlite3_prepare_v2(vDB, pCommand.cString(using: .utf8), -1, &vInsertStatement, nil))")
vReturn = false
}
sqlite3_finalize(vInsertStatement)
sqlite3_close(vDB)
return vReturn
}
If I print the sqlite3_errmsg(vDB) in the console I get this that does not help:
▿ Optional> ▿ some : 0x0000000105347e80
- pointerValue : 4382293632
If I print sqlite3_step(vInsertStatement) returns 21 SQLITE_MISUSE
Any help will be appreciated.
Thanks in advance.
I just found the problem, we don´t have permission of writing in the main bundle.
So you have to copy the DB first, here is the code:
private static func copyFromBundle () {
let vBundlePath = Bundle.main.path(forResource: "ARDB", ofType: ".db")
let vDestPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
let vFileManager = FileManager.default
let vFullDestPath = URL(fileURLWithPath: vDestPath).appendingPathComponent("ARDB.db")
mDocumentsPath = ""
if vFileManager.fileExists(atPath: vFullDestPath.path){
print("Database file exist don´t copy.")
mDocumentsPath = vFullDestPath.path
} else {
if vFileManager.fileExists(atPath: vBundlePath!) {
do {
try vFileManager.copyItem(atPath: vBundlePath!, toPath: vFullDestPath.path)
print("Database file does´t exist copy it.")
mDocumentsPath = vFullDestPath.path
} catch {
print("copyFromBundle() fail with error: ",error)
}
}
}
}
Thanks for the help.
Happy coding.
I've created an app using Swift3 and Xcode8, and using FMDB as my database, when it runs on the simulator it can get the data from data.db,but when it runs on the generic device (which is my phone), there's no data in the tableView, also could't insert records. I added data.db into my project, but when I changed records on simulator, records in data.db didn't change, but I printed the path, it pointed to simulator folder, database in that folder will changed along the modify in simulator and that folder's path changes almost every time. I'm so confused, is that because I didn't connected to my database in fact?
Here's the Utility.Swift which holds common and often reused function
import UIKit
class Utility: NSObject {
class func getPath(_ fileName: String) -> String {
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileURL = documentsURL.appendingPathComponent(fileName)
print(fileURL.path)
return fileURL.path
}
class func copyFile(_ fileName: NSString){
let dbPath: String = getPath(fileName as String)
let fileManager = FileManager.default
if !fileManager.fileExists(atPath: dbPath) {
let documentsURL = Bundle.main.resourceURL
let fromPath = documentsURL!.appendingPathComponent(fileName as String)
var error : NSError?
do {
try fileManager.copyItem(atPath: fromPath.path, toPath: dbPath)
}
catch let error1 as NSError {
error = error1
}
let alert: UIAlertView = UIAlertView()
if (error != nil) {
alert.title = "Error Occured"
alert.message = error?.localizedDescription
}
else {
alert.title = "Successfully Copy"
alert.message = "Your database copy successfully"
}
alert.delegate = nil
alert.addButton(withTitle: "Ok")
alert.show()
}
}
class func invokeAlertMethod(_ strTitle: NSString, strBody: NSString, delegate: AnyObject?) {
let alert: UIAlertView = UIAlertView()
alert.message = strBody as String
alert.title = strTitle as String
alert.delegate = delegate
alert.addButton(withTitle: "Ok")
alert.show()
}
}
and StudentDataBase.swift contains Query languages
import UIKit
let sharedInstance = StudentDataBase()
class StudentDataBase : NSObject {
var database: FMDatabase? = nil
class func getInstance() -> StudentDataBase{
if((sharedInstance.database) == nil)
{
sharedInstance.database = FMDatabase(path: Utility.getPath("data.db"))
}
return sharedInstance
}
func addStuData(_ student: Student) -> Bool{
sharedInstance.database!.open()
let isInserted = sharedInstance.database!.executeUpdate("INSERT INTO [Student info] (StudentID, FirstName, LastName, PhoneNumber) VALUES (?, ?, ?, ?)", withArgumentsIn: [student.studentID, student.fstName, student.lstName, student.phoneNum])
sharedInstance.database!.close()
return isInserted
}
func updateStuData(_ student: Student) -> Bool {
sharedInstance.database!.open()
let isUpdated = sharedInstance.database!.executeUpdate("UPDATE [Student info] SET FirstName=?, LastName=?, PhoneNumber=? WHERE StudentID=?", withArgumentsIn: [student.fstName, student.lstName, student.phoneNum, student.studentID])
print(student)
print(isUpdated)
sharedInstance.database!.close()
return isUpdated
}
func deleteStuData(_ student: Student) -> Bool {
sharedInstance.database!.open()
let isDeleted = sharedInstance.database!.executeUpdate("DELETE FROM [Student info] WHERE StudentID=?", withArgumentsIn: [student.studentID])
sharedInstance.database!.close()
return isDeleted
}
func getAllStuData() -> [Student] {
sharedInstance.database!.open()
let resultSet: FMResultSet! = sharedInstance.database!.executeQuery("SELECT * FROM [Student info]", withArgumentsIn: nil)
var marrStudentInfo : [Student] = []
if (resultSet != nil) {
while resultSet.next() {
let student : Student = Student()
student.studentID = resultSet.string(forColumn: "StudentID")
student.fstName = resultSet.string(forColumn: "FirstName")
student.lstName = resultSet.string(forColumn: "LastName")
student.phoneNum = resultSet.string(forColumn: "PhoneNumber")
marrStudentInfo.append(student)
}
}
sharedInstance.database!.close()
return marrStudentInfo
}
}
also in AppDelegate.swift, I've written:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
Utility.copyFile("data.db")
return true
}
I'm new to Swift and FMDB, please explain and I'll try my best.Thank you very much!!!!
Edit:
After I downloaded the contents in my phone and the database is blank. And after I throwaway the error, it shows
Inserted failed:Optional("no such table: Student info")
SQLite offers good error reporting and you should avail yourself of it. So, for example, if any of these executeUpdate calls fail, you should examine the lastErrorMessage (before you close the database) so you know why it failed.
Personally, I'd suggest using executeQuery(_:values:) and executeUpdate(_:values:) rather than executeQuery(_:withArgumentsIn:) and executeUpdate(_:withArgumentsIn:) because the former methods throw errors (which shifts to a more active error handling paradigm from the more passive one in which you have to manually remember to check for errors). But regardless of how you do your error handling, do it, or else we're all guessing.
Personally, I'd suggest using Xcode's "Devices" window and download your app's bundle (see https://stackoverflow.com/a/38064225/1271826) and look at the database. I bet it's a blank database with no tables defined at all. In terms of why, it could be because you did some earlier testing and there's an old version of the database in the Documents folder. Try completely deleting the app from the device and re-running and see if that fixes it.
Other, more obscure sources of problem include filename capitalization (e.g. Data.db vs data.db). The macOS file system will often handle that gracefully because it's not (generally) case sensitive. But iOS devices are case sensitive.
But, like I said, we're flying blind until (a) you add some error handling to your code so you can see why its failing; and (b) look at the database on the device to see what it looks like.
I'm trying to learn Swift and so I wrote a little test application to that end. It just gives the total size of items in a directory, recursing into subdirectories to accumulate the total size of the their contents. The application works, but the memory usage just grows and grows while it runs. I had expected the memory usage to increase as the recursion got deeper and decrease when a recursive call returns. Instead, the memory usage just constantly climbs. Instruments doesn't identify any leaks. I've tried a few tips I've found in various google results, including:
re-using the default NSFileManager
not re-using the default NSFileManager but creating a new one for each recursive call
avoiding String interpolation
Nothing seems to make any difference. I had thought that Swift would clean up objects as their reference count reached zero.
This is the code in its entirety in its current state:
import Foundation
func sizeOfContents(path: String) -> UInt64
{
let subManager = NSFileManager()
var totalSize: UInt64 = 0;
var isDir: ObjCBool = false
if subManager.fileExistsAtPath(path, isDirectory: &isDir)
{
if !isDir.boolValue
{
var error: NSError? = nil
let attributes: NSDictionary? = subManager.attributesOfItemAtPath(path, error: &error)
let size: UInt64? = attributes?.fileSize()
totalSize += size!
}
else
{
var error: NSError? = nil
if let subContents = subManager.contentsOfDirectoryAtPath(path, error: &error)
{
for subItem in subContents
{
var subName = subItem as String
subName = path + "/" + subName
totalSize += sizeOfContents(subName)
}
}
}
}
return totalSize
}
let manager = NSFileManager.defaultManager()
var rootPath = "/Applications/"
if let contents = manager.contentsOfDirectoryAtPath(rootPath, error: nil)
{
for item in contents
{
let itemName = item as String
var isDir: ObjCBool = false
print("item: " + (rootPath + itemName))
if manager.fileExistsAtPath(rootPath + itemName, isDirectory: &isDir)
{
if !isDir.boolValue
{
var error: NSError? = nil
let attributes: NSDictionary? = manager.attributesOfItemAtPath(rootPath + itemName, error: &error)
let size: UInt64? = attributes?.fileSize()
println("\t\(size!)")
}
else
{
if(itemName != "Volumes")
{
let size = sizeOfContents(rootPath + itemName)
println("\t\(size)")
}
}
}
}
}
You need to add an autoreleasepool in the loop, possibly around the recursive call. Since it is a tight loop the idle loop is not getting a change to release the temp memory allocations.
example:
...
autoreleasepool {
totalSize += sizeOfContents(subName)
}
...