Подключение 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().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)       // создание таблицы <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("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() 

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

ссылка на оригинал статьи https://habrahabr.ru/post/277423/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *