[sqlite] Swift에서 SQLite 데이터베이스에 액세스

Swift 코드로 내 앱에서 SQLite 데이터베이스에 액세스하는 방법을 찾고 있습니다.

Objective C에서 SQLite Wrapper를 사용하고 브리징 헤더를 사용할 수 있다는 것을 알고 있지만,이 프로젝트를 Swift에서 완전히 수행 할 수 있기를 바랍니다. 이 작업을 수행하는 방법이 있습니까? 누군가 쿼리를 제출하고 행을 검색하는 방법을 보여주는 참조를 알려줄 수 있습니까?



답변

많은 SQLite 래퍼 중 하나를 사용해야하지만 SQLite 라이브러리를 직접 호출하는 방법을 알고 싶다면 다음을 수행합니다.

  1. SQLite C 호출을 처리하도록 Swift 프로젝트를 구성하십시오. Xcode 9 이상을 사용하는 경우 다음을 수행 할 수 있습니다.

    import SQLite3
  2. 데이터베이스 생성 / 열기.

    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
    }

    참고로, 데이터베이스를 열지 못했을 때 데이터베이스를 닫는 것이 이상하게 보이지만 sqlite3_open 문서 에는 메모리 누수를 피하기 위해 그렇게해야한다고 명시되어 있습니다.

    열릴 때 오류가 발생하는지 여부에 관계없이 데이터베이스 연결 핸들 과 관련된 자원 sqlite3_close()은 더 이상 필요하지 않을 때 전달하여 해제해야 합니다.

  3. sqlite3_execSQL을 수행하는 데 사용 합니다 (예 : 테이블 생성).

    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)")
    }
  4. 값을 바인딩 할 자리 표시 자로 sqlite3_prepare_v2SQL을 준비하는 데 사용 ?합니다.

    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)")
    }

    다음과 같이 구현할 수있는SQLITE_TRANSIENT 상수 를 사용합니다 .

    internal let SQLITE_STATIC = unsafeBitCast(0, to: sqlite3_destructor_type.self)
    internal let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
  5. 다른 값을 삽입하려면 SQL을 재설정하십시오. 이 예에서는 NULL값을 삽입 합니다.

    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)")
    }
  6. 준비된 명령문과 연관된 메모리를 복구하기 위해 준비된 명령문을 완료하십시오.

    if sqlite3_finalize(statement) != SQLITE_OK {
        let errmsg = String(cString: sqlite3_errmsg(db)!)
        print("error finalizing prepared statement: \(errmsg)")
    }
    
    statement = nil
  7. 테이블에서 값을 선택하기위한 새 문을 준비하고 값 검색을 통해 반복합니다.

    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
  8. 데이터베이스 닫기 :

    if sqlite3_close(db) != SQLITE_OK {
        print("error closing database")
    }
    
    db = nil

Swift 2 및 이전 버전의 Xcode에 대해서는 이 답변의 이전 개정판을 참조하십시오 .


답변

가장 좋은 방법은 브리징 헤더 안에 동적 라이브러리를 가져 오는 것입니다.

  1. “Link Binary With Libraries”빌드 단계에 libsqlite3.dylib를 추가합니다.
  2. “Bridging-Header.h”를 만들고 #import <sqlite3.h>상단에 추가
  3. “Swift 컴파일러-코드 생성”아래의 빌드 설정에서 “Objective-C 브리징 헤더”설정에 대해 “Bridging-Header.h”를 설정합니다.

그러면 sqlite3_open신속한 코드에서 와 같이 모든 c 메서드에 액세스 할 수 있습니다.

그러나 FMDB 를 사용 하고 sqlite의 객체 지향 래퍼 인 브리징 헤더를 통해 가져올 수 있습니다. C 포인터와 구조체를 다루는 것은 Swift에서 번거로울 것입니다.


답변

저도 이전에 Objective-C에서했던 것과 같은 방식으로 SQLite와 상호 작용할 수있는 방법을 찾고있었습니다. C 호환성 때문에 저는 그냥 C API를 사용했습니다.

현재 Swift에 SQLite에 대한 래퍼가없고 위에서 언급 한 SQLiteDB 코드가 약간 더 높은 수준으로 이동하고 특정 사용을 가정하므로 래퍼를 만들고 그 과정에서 Swift에 약간 익숙해지기로 결정했습니다. 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 */


답변

SwiftData 라는 Swift로 완전히 작성된 우아한 SQLite 라이브러리를 만들었습니다 .

그 기능 중 일부는 다음과 같습니다.

  • 객체를 SQL 문자열에 편리하게 바인딩
  • 트랜잭션 및 저장 점 지원
  • 인라인 오류 처리
  • 기본적으로 완전히 스레드 안전

‘변경'(예 : INSERT, UPDATE, DELETE 등)을 실행하는 쉬운 방법을 제공합니다.

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
}

및 ‘쿼리'(예 : 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)")
        }
    }
}

더 많은 기능과 함께!

여기에서 확인할 수 있습니다.


답변

Swift 2 및 Swift 3을위한 또 다른 SQLite 래퍼 : http://github.com/groue/GRDB.swift

풍모:

  • ccgus / fmdb 사용자에게 친숙한 API

  • Swift 표준 라이브러리를 활용하는 저수준 SQLite API

  • SQL 알레르기 개발자를위한 매우 신속한 쿼리 인터페이스

  • SQLite WAL 모드 지원 및 추가 성능을위한 동시 데이터베이스 액세스

  • 결과 집합을 래핑하고, 아침 식사를 위해 사용자 지정 SQL 쿼리를 먹고, 기본 CRUD 작업을 제공하는 레코드 클래스

  • 신속한 유형 자유 : 데이터에 맞는 올바른 Swift 유형을 선택하십시오. 필요할 때 Int64를 사용하거나 편리한 Int를 고수하십시오. NSDate 또는 NSDateComponents를 저장하고 읽습니다. 이산 데이터 유형에 대한 Swift 열거 형을 선언합니다. 고유 한 데이터베이스 변환 가능 유형을 정의하십시오.

  • 데이터베이스 마이그레이션

  • 속도 : 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 = ""
}

액세스 데이터베이스 :

let DB=database()
var mod=Model()

데이터베이스 쿼리 실행 :

var DailyResult:Array<Model> = DB.AllSelectQuery("select * from food where foodtype == 'Sea Food' ORDER BY name ASC")


답변

이것은 내가 Swift에서 사용한 최고의 SQLite 라이브러리입니다 : https://github.com/stephencelis/SQLite.swift

코드 예제를보십시오. 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"

문서에는 또한 “SQLite.swift는 C API를 통해 가볍고 Swift 친화적 인 래퍼로도 작동합니다.”라고 나와 있으며 그에 대한 몇 가지 예가 뒤 따릅니다.