xcode swift SQLite3 CRUD - 1.Create

DB를 공부함에 있어서 가장 먼저 해야 할 것은 CRUD라고 생각한다. 그래서 이번에 로컬 DB도 공부해볼겸 SQLite3 CRUD도 조금 절리를 해둬야겠다는 생각에 글을 쓴다.

글은 크게 DB를 만들고 Table을 만들고, 데이터를 넣고, 읽고, 수정하고, 삭제해 볼 것이다.

DBHelper

init

먼저 DBHelper.swift 파일을 하나 만들어주자. 그리고 해당 파일 안에 Foundation 과 SQLite3 를 import하고, 같은 이름의 class DBHelper{} 를 적어준다. 그런 다음, init() 메소드를 하나 넣어주자. 최종적으로 다음과 같은 코드가 만들어지면 된다.

import Foundation
import SQLite3

class DBHelper {
  init() {

  }
}

 

db 참조자 만들고 DB 생성하기

Init() 메서드와 class DBHelper { 사이에 db 참조자를 만들어주자. 필자는 var db: OpaquePointer? 이런 식으로 만들어주었다.

참고로 여기서 OpaquePointer는 간단히 말해 가져다쓰는 자료형을 가리키는 포인터 정도로 생각하면 되겠다. 좀더 tmi로 말하자면 우리가 프로그램을 만들다 보면 다른 곳에서 만든 라이브러리를 가져다 쓰거나, 운영체제에서 제공해주는 API를 사용하는 경우가 대부분인데, 이러한 라이브러리나 API에서 제공해주는 함수를 사용하려면 그 라이브러리, API에서 제공해주는 자료형을 사용해야 하는 경우가 많다. 그런데 이 자료형들은 대부분 구조체가 아닌 포인터이다. 구조체로 정의되어 있지 않기 때문에 내부가 어떤 멤버들로 구성되어 있는지 알 방법이 없다는 특징이 있다. 이런 것들을 오파크 타입(Opaque Type)이라고 한다.

 

참조자를 만들어주었다면 init() 메소드 밑에 DB를 create하는 메서드를 하나 만들어주자. 이 함수는 OpaquePointer? 를 리턴해줄 것이다. 여기까지 따라했다면 다음과 같은 코드가 되어 있어야 한다:

import Foundation
import SQLite3

class DBHelper {
     var db: OpaquePointer?
  init() {

  }
  func createDB() -> OpaquePointer? {

  }
}

 

db 이름 만들어주기.

우리가 DB를 생성하기 이전에 db의 이름은 무엇으로 할 것인지 설정해주자. 단순히 어떤 String형 변수를 선언하고 거기에 이름을 담아주면 된다. 필자는 var path: String = "crudTestDB.sqlite" 라고 적어줬다. 확장자는 반드시 sqlite여야 한다.

 

파일 경로 설정하기

FileManager 를 이용하여 DB가 위치할 경로를 지정해줄 것이다. 코드는 다음과 같으며, 이것이 무슨 뜻인지 잘 모르겠다고 하시는 분들은 여기 블로그 를 참고하자. 잘 이해하고 있다면 아래 코드가 Document 디렉토리 내에 생성될 것이라는 게 이해가 될 것이다.

func createDB() -> OpaquePointer? {
  let filePath = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathExtension(path)

}

 

새로운 OpaquePointer 선언하고 sqlite3_open을 이용해 db 생성

새로운 변수를 하나 선언해주자. 바로 위에서 구현한 filePath 바로 밑에 var db: OpaquePointer? = nil 과 같이 OpaquePointer? 타입의 변수를 하나 선언해주면 된다.

선언했다면, sqlite3_open 메소드를 이용해 filePath에 접근하여 db를 만들고, 그 결과를 우리가 선언한 변수(위의 db)에 넣어줄 것이다. 또한 이것이 실패했을 때 오류메시지를 출력하도록 만들어줘보자. 코드는 아래와 같다.

func createDB() -> OpaquePointer? {
  let filePath = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathExtension(path)

  var db: OpaquePointer? = nil

  if sqlite3_open(filePath.path, &db) != SQLITE_OK {
    print("Error while creating db")
    return nil
  } else {
    print("Database has been created with path \(path)")
    return db
  }
}

 

여기까지 전체코드는 다음과 같다

import Foundation
import SQLite3

class DBHelper {
    var db: OpaquePointer?
    var path: String = "crudTestDB.sqlite"

    init() {

    }

    func createDB() -> OpaquePointer? {
        let filePath = try! FileManager.default.url(for: .documentDirectory,    in: .userDomainMask, appropriateFor: nil, create:   false).appendingPathExtension(path)

        var db: OpaquePointer? = nil

        if sqlite3_open(filePath.path, &db) != SQLITE_OK {
          print("Error while creating db")
          return nil
        } else {
          print("Database has been created with path \(path)")
          return db
        }
    }
}

 

Table 만들기

하나의 Database는 여러 가지 table을 가질 수 있다. Table도 한번 만들어보자.

먼저 함수를 생성하기 위한 메소드를 하나 만들어준다. func createTable() 정도가 좋겠다.

 

그리고 그 함수 안에 테이블을 만들기 위한 쿼리 텍스트를 하나 적어주자.

만약 id, longtitude, latitude 라는 필드를 갖는 pedo라는 이름의 테이블을 만든다면 다음과 같은 쿼리를 날려야 할 것이다.

"CREATE TABLE IF NOT EXISTS pedo(id INTEGER PRIMARY KEY AUTOINCREMENT, latitude DOUBLE, longtitude DOUBLE);"

이것을 그냥 var createTableQuery = 뒤에 적어주면 끝.

 

그리고 여기서 우리는 OpaquePointer를 하나 더 선언해주고, sqlite3_prepare_v2 를 통해 table을 생성해줄 것이다.

일단 그 과정을 하나하나 설명하기는 힘들 것 같아서 코드를 통째로 공개한다.

    func createTable() {
        let createTableQuery = "CREATE TABLE IF NOT EXISTS pedo(id INTEGER PRIMARY KEY AUTOINCREMENT, latitude DOUBLE, longtitude DOUBLE);"

        var createTablePtr: OpaquePointer? = nil

        if sqlite3_prepare_v2(self.db, createTableQuery, -1, &createTablePtr, nil) == SQLITE_OK {
            if sqlite3_step(createTablePtr) == SQLITE_DONE {
                print("table creation has been successfully done")
            }
            else {
                print("table creation failure")
            }
        } else {
            print("Preparation for creating table has been failed")
        }
    }

 

sqlite3_prepare_v2

위 코드에서 sqlite3_prepare_v2() 메서드의 인자는 각각 무엇을 의미할까?

공식문서 에 따르면 다음과 같은 설명을 적어놓았다.

int sqlite3_prepare_v2(
  sqlite3 *db,            /* Database handle */
  const char *zSql,       /* SQL statement, UTF-8 encoded */
  int nByte,              /* Maximum length of zSql in bytes. */
  sqlite3_stmt **ppStmt,  /* OUT: Statement handle */
  const char **pzTail     /* OUT: Pointer to unused portion of zSql */
);

 

여기서 sqlite3_prepare_v2는 2번째 인자로 들어온 쿼리 스트링을 컴파일하고, 4번째 인자는 컴파일된 쿼리를 가리키는 포인터가 된다. 3번째 인자는 2번째 인자로 들어온 쿼리의 길이인데, -1을 적어주면 자동계산한다. 5번째 인자는 destructor라고 해서 문자열을 해제하는 데에 쓰이는 함수이지만 쓸일이 없기 때문에 nil을 적어주었고 첫 번째 인자는 어느 db에 쓸 것인지를 지정한다. 마지막으로 sqlite3_step() 은 쿼리를 실행하는데, 이 쿼리를 가리키는 포인터가 네 번째 인자로 넣어준 녀석이므로 그것을 넣어준다.

 

여기까지 잘 따라왔다면 다음과 같은 형태의 코드가 만들어져야 한다.

빼먹은 게 있다면 붙여넣으시길.

import Foundation
import SQLite3

class DBHelper {
    var db: OpaquePointer?
    var path: String = "crudTestDB.sqlite"

    init() {

    }

    func createDB() -> OpaquePointer? {
        let filePath = try! FileManager.default.url(for: .documentDirectory,    in: .userDomainMask, appropriateFor: nil, create:   false).appendingPathExtension(path)

        var db: OpaquePointer? = nil

        if sqlite3_open(filePath.path, &db) != SQLITE_OK {
          print("Error while creating db")
          return nil
        } else {
          print("Database has been created with path \(path)")
          return db
        }
    }

    func createTable() {
        let createTableQuery = "CREATE TABLE IF NOT EXISTS pedo(id INTEGER PRIMARY KEY AUTOINCREMENT, latitude DOUBLE, longtitude DOUBLE);"

        var createTablePtr: OpaquePointer? = nil

        if sqlite3_prepare_v2(self.db, createTableQuery, -1, &createTablePtr, nil) == SQLITE_OK {
            if sqlite3_step(createTablePtr) == SQLITE_DONE {
                print("table creation has been successfully done")
            }
            else {
                print("table creation failure")
            }
        } else {
            print("Preparation for creating table has been failed")
        }
    }
}

 

init() 에서 함수 호출

만들어준 함수들을 호출하자.

init() 메소드 안에 다음 코드를 추가하면 DB를 만들고 Table을 만드는 것은 모두 끝이다.

init() {
  self.db = createDB()
  self.createTable()
}

 

여기까지 따라하고, 우리가 DB 를 생성하고자 하는 ViewController 안에 let db = DBHelper() 와 같이 써주면 DB가 해당 시점에 생성될 것이다. 한번 시도해보자.

  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기

댓글을 달아 주세요

">