Підключення SQLLite до мобільного додатка iOS через FMDB на Xcode використовуючи Swift

Зіткнувшись із завданням підключити SQLLite до свого мобільного додатка iOS через FMDB, я не знайшов ні одного актуального гайда російською мовою. І тим більше для Swift. У цій статті я постараюся цього виправити.

У цьому гайде будуть використовуватися файли з objective-c, тому не потрібно чекати порту FMDB на Swift.

Скачати FMDB можна тут.

У FMDB три main class:

FMDatabase — являє даних SQLite. Використовується для виконання SQL-операторів.
FMResultSet — представляє результати виконання запиту за FMDatabase.
FMDatabaseQueue — якщо ви хочете, щоб виконувалися запити і оновлення на кілька потоків, можна використовувати цей клас. Приклад у 8 пункті.

Перш ніж ви зможете взаємодіяти з базою даних, вона має бути відкрита. Відкриття завершитися з помилкою, якщо немає достатніх ресурсів або дозволу на відкриття та/або створення бази даних.

if (![db open]) {
[db release];
return;
}

Кроки:

1) Додайте 'libsqlite3' стандартну бібліотеку в настроюваннях проекту та скопіюйте FMDB файли в ваш проект. (так, вони objective-c).

2) Створіть новий файл, який буде називатися «FMDB-Bridging-Header.h». Всередині «Bridging-Header.h» напишіть наступне: #import «FMDB.h».

3) Зайдіть в Build Settings -> Swift Compiler — Code Generation і додайте до 'Objective-C Bridging Header': FMDB-Bridging-Header.h.

Якщо файл у папці вашого проекту, то так: ІМ'Я_ПАПКИ/FMDB-Bridging-Header.h

4) Скопіюйте в Ваш проект SQLite database. У цьому гайде я буду використовувати назву 'tempdb.sqlite' всього лише з однією таблицею всередині:

CREATE TABLE test_tb ( test_id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, keywordtext TEXT)

5) У вашому AppDelegate.swift's class AppDelegate додайте наступні змінні: var dbFilePath: NSString = NSString()

Приклад:
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?
var navi: UINavigationController?
var dbFilePath: NSString = NSString()

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
....

6) Додайте цей метод в AppDelegate.swift's class AppDelegate:

// MARK: - FMDB

let DATABASE_RESOURCE_NAME = "tempdb"
let DATABASE_RESOURCE_TYPE = "sqlite"
let DATABASE_FILE_NAME = "tempdb.sqlite"

func initializeDb() -> Bool {
let documentFolderPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String

let dbfile = "/" + DATABASE_FILE_NAME;

self.dbFilePath = documentFolderPath.stringByAppendingString(dbfile)

let filemanager = NSFileManager.defaultManager()
if (!filemanager.fileExistsAtPath(dbFilePath) ) {

let backupDbPath = NSBundle.mainBundle().pathForResource(DATABASE_RESOURCE_NAME, ofType: DATABASE_RESOURCE_TYPE)

if (backupDbPath == nil) {
return false
} else {
var error: NSError?
let copySuccessful = filemanager.copyItemAtPath(backupDbPath, toPath:dbFilePath, error: &error)
if !copySuccessful {
println("copy failed: \(error?.localizedDescription)")
return false
}

}

}
return true

}

7) Викличте в AppDelegate.swift's func application:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {

if self.initializeDb() {
NSLog("Successful db copy")
}

8) У цьому прикладі ми працюємо з даними UITableViewController) використовуючи FMDB:

import UIKit

class SecondViewController: UIViewController {

// MARK: - .H

@IBOutlet var dataTable: UITableView?
var dataArray:[MultiField] = []

// MARK: - .M

required init(coder: NSCoder) {
fatalError("NSCoding not supported")
}

override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
// Custom initialization
}

override func viewDidLoad() {
super.viewDidLoad()

// Do any additional setup after loading the view.
self.title = "FMDB Using Swift"

let mainDelegate: AppDelegate = UIApplication.sharedApplication().представник as AppDelegate

// initialize FMDB
let db: FMDatabase = FMDatabase(path:mainDelegate.dbFilePath)
if (db.open() == nil) {
NSLog("error opening db")
}

// вставка даних
let addQuery = "INSERT INTO test_tb (name, keywordtext) VALUES ('excalibur', 'hot')"
let addSuccessful = db.executeUpdate(addQuery, withArgumentsInArray: nil)
if !addSuccessful {
println("insert failed: \db.lastErrorMessage())")
}
// вставка даних - кінець

// update даних
let updateQuery = "UPDATE test_tb SET keywordtext = 'cool' WHERE name = 'excalibur' "
let updateSuccessful = db.executeUpdate(updateQuery, withArgumentsInArray: nil)
if !updateSuccessful {
println("update failed: \db.lastErrorMessage())")
}
// update даних - кінець

// Отримання даних з нашої бази і збереження їх в масив UITableView
let mainQuery = "SELECT name, keywordtext FROM test_tb"
let rsMain: FMResultSet? = db.executeQuery(mainQuery, withArgumentsInArray: [])

while (rsMain!.next() == true) {
let productName = rsMain?.stringForColumn("name")
let keywords = rsMain?.stringForColumn("keywordtext")

let multiField = MultiField(aField1: productName!, aField2: keywords!)
self.dataArray.append(multiField)

}
// отримання даних - кінець

// видалення даних
let delQuery = "DELETE FROM test_tb WHERE name = 'excalibur' "
let deleteSuccessful = db.executeUpdate(delQuery, withArgumentsInArray: nil)
if !deleteSuccessful {
println("delete failed: \db.lastErrorMessage())")
}
// видалення даних - кінець

// приклад: отримання номер рядків
let rsTemp: FMResultSet? = db.executeQuery("SELECT count(*) AS numrows FROM test_tb", withArgumentsInArray: [])
rsTemp!.next()
let numrows = rsTemp?.intForColumn("numrows")

NSLog("numrows: \(numrows)")
//приклад: отримання номер рядки - кінець

db.close()

}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}

// MARK: - TableView DataSource
func numberOfSectionsInTableView(tableView: UITableView!) -> Int {
return 1
}

func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {

return self.dataArray.count
}

func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {

let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "FMDBTest")

let multiField: MultiField = self.dataArray[indexPath.row]

let num = indexPath.row + 1

cell.текст мітки.text = "\(num). \(multiField.field1!)"
cell.detailTextLabel.text = multiField.field2

return cell
}
// MARK: - UITableViewDelegate

func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
}

9) Трохи різних фішок, використання мультипотока FMDB через FMDatabaseQueue.

var queue: FMDatabaseQueue?

func testDatabaseQueue() {
let documentsFolder = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String
let databasePath = documentsFolder.stringByAppendingPathComponent("test.sqlite")

queue = FMDatabaseQueue(path: databasePath)

// створення таблиці
<source lang="objectivec"> 
queue?.inDatabase() {
db in

var success = db.executeUpdate("create table test (id integer primary key autoincrement, a text)", withArgumentsInArray:nil)

if !success {
println("create table failure: \db.lastErrorMessage())")
return
}
}

// вставка п'яти рядків

queue?.inTransaction() {
db, rollback in

for i in 0 ..< 5 {
if !db.executeUpdate("insert into test (a) values (?)", withArgumentsInArray: ["Row \(i)"]) {
println("insert \(i) failure: \db.lastErrorMessage())")
rollback.initialize(true)
return
}
}
}

//давайте спробуємо вставити рядки, але свідомо помилимося і подивимося, що він відкатує правильно

queue?.inTransaction() {
db, rollback in

for i in 5 ..< 10 {
let success = db.executeUpdate("insert into test (a) values (?)", withArgumentsInArray: ["Row \(i)"])

if !success {
println("insert \(i) failure: \db.lastErrorMessage())")
rollback.initialize(true)
return
}

if (i == 7) {
rollback.initialize(true)
}
}
}

// перевіримо, що тільки перші п'ять рядків там

queue?.inDatabase() {
db in

if let rs = db.executeQuery("select * from test", withArgumentsInArray:nil) {

while rs.next() {
println(rs.resultDictionary())
}
} else {
println("select failure: \db.lastErrorMessage())")
}

}

// видаляємо таблицю

queue?.inDatabase() {
db in

let success = db.executeUpdate("drop table test", withArgumentsInArray:nil)

if !success {
println("drop table failure: \db.lastErrorMessage())")
return
}
}
}

10) Стандартного на закуску. Використання класу executeUpdate(values:) в Swift2:

do {
let identifier = 42
let name = "Liam O Flaherty (\"the famous Irish author\")"
let date = NSDate()
let comment: String? = nil

try db.executeUpdate("INSERT INTO authors (identifier, name, date, comment) VALUES (?, ?, ?, ?)", values: [identifier, name, date, comment ?? NSNull()])
} catch {
print("error = \(error)")
}

Використання queue:

queue.inTransaction { db, rollback in
do {
try db.executeUpdate("INSERT INTO myTable VALUES (?)", values: [1])
try db.executeUpdate("INSERT INTO myTable VALUES (?)", values: [2])
try db.executeUpdate("INSERT INTO myTable VALUES (?)", values: [3])

if whoopsSomethingWrongHappened {
rollback.memory = true
return
}

try db.executeUpdate("INSERT INTO myTable VALUES (?)", values: [4])
} catch {
rollback.memory = true
print(error)
}
}

Приклад із стандартного опису:

let documents = try! NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: false)
let fileURL = documents.URLByAppendingPathComponent("test.sqlite")

let database = FMDatabase(path: fileURL.path)

if !database.open() {
print("Unable to open database")
return
}

do {
try database.executeUpdate("create table test(text x, y, text, z text)", values: nil)
try database.executeUpdate("insert into test (x, y, z) values (?, ?, ?)", values: ["a", "b", "с"])
try database.executeUpdate("insert into test (x, y, z) values (?, ?, ?)", values: ["е", "f", "g"])

let rs = try database.executeQuery("select x, y, z from test", values: nil)
while rs.next() {
нехай x = rs.stringForColumn("x")
let y = rs.stringForColumn("y")
let z = rs.stringForColumn("z")
print("x = \(x); y = \(y); z = \(z)")
}
} catch let error as NSError {
print("failed: \(error.localizedDescription)")
}

database.close()

Якщо щось не виходить, пишете, постараюся допомогти.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.