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

Столкнувшись с задачей подключить SQLLite к своему мобильному приложению iOS через FMDB, я не нашел ниодного актуального гайда на русском языке. И тем более для Swift. В этой статье я постараюсь этого исправить.

Моя статья на хабре https://habrahabr.ru/post/277423/

В этом гайде будут использоваться файлы с objective-c, поэтому не надо ждать порта FMDB на Swift.

Скачать FMDB можно тут https://github.com/ccgus/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().delegate 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.textLabel.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)
 
// создание таблицы

 
queue?.inDatabase() {
db in
 
var success = db.executeUpdate(«create table test (id integer primary key autoincrement, a text)», withArgumentsInArray:nil)
 
if !success {
println(«table create 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(«table drop 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(x text, y text, z text)», values: nil)
try database.executeUpdate(«insert into test (x, y, z) values (?, ?, ?)», values: [«a», «b», «c»])
try database.executeUpdate(«insert into test (x, y, z) values (?, ?, ?)», values: [«e», «f», «g»])

let rs = try database.executeQuery(«select x, y, z from test», values: nil)
while rs.next() {
let 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()

Если что-то не получается, пишете, постараюсь помочь.