FMDB從理論到實戰(zhàn)(swift4)

理論篇

本文默認(rèn)你已經(jīng)在項目中繼承了FMDB,如果是swift項目,已經(jīng)在swift中引入橋接文件并添加了FMDB。
iOS數(shù)據(jù)庫常用的庫有CoreData、FMDB、Realm等。對于三種方式的優(yōu)缺點及速度等本文不做討論,由于FMDB的受歡迎程度很高,本文對FMDB進(jìn)行探討和梳理。

首先三種數(shù)據(jù)庫操作方式的簡介:

  1. CoreData是Apple對sqlite的封裝,一種面向?qū)ο蟮臄?shù)據(jù)庫操作方式。包含.xcdatamodeld的模型可視化操作,多表的關(guān)聯(lián),數(shù)據(jù)的變更檢測等,功能強大。了解更多 認(rèn)識CoreData-基礎(chǔ)使用
  2. FMDB是開源的對sqlite的封裝,使用sql語句進(jìn)行對數(shù)據(jù)庫的操作,靈活方便,本文主要討論這個。了解更多SQLite的常見問題
  3. Realm是由Y Combinator孵化的創(chuàng)業(yè)團隊開源出來的一款可以用于iOS(同樣適用于Swift&Objective-C)和Android的跨平臺移動數(shù)據(jù)庫。目前最新版是Realm 2.0.2,支持的平臺包括Java,Objective-C,Swift,React Native,Xamarin。提供可視化工具。Realm與sqlite無關(guān)。了解更多Realm數(shù)據(jù)庫 從入門到“放棄”
FMDB主要類
  1. FMDatabase-代表一個獨立的SQLite數(shù)據(jù)庫,執(zhí)行SQL語句。
  2. FMResultSet-代表FMDatebase查詢的結(jié)果集,
  3. FMDatabaseQueue-如果你想要在多線程中查詢和更新,你應(yīng)該使用這個類,
FMDB創(chuàng)建數(shù)據(jù)庫

FMDatabase通過一個SQLite數(shù)據(jù)庫文件的路徑創(chuàng)建。這個路徑可以有以下三種樣式:

  1. 一個系統(tǒng)文件路徑.硬盤上之前不存在的,如果它不存在,F(xiàn)MDB會為你新建。
  2. 一個空字符串,一個空數(shù)據(jù)庫在臨時文件中創(chuàng)建。在數(shù)據(jù)庫連接關(guān)閉的時候,這個數(shù)據(jù)庫會被刪除。
  3. NULL(空)。一個在內(nèi)存中的數(shù)據(jù)庫將被創(chuàng)建。在數(shù)據(jù)庫連接關(guān)閉的時候,這個數(shù)據(jù)庫會被銷毀。
    eg:
//OC
FMDatabase *db = [FMDatabase databaseWithpath:@"/tmp/tmp.db"];
//想了解更多關(guān)于臨時或內(nèi)存數(shù)據(jù)庫,請閱讀sqlite文檔:http://www.sqlite.org/inmemorydb.html
//swift
let db = FMDatabase.init(path: "/tmp/tmp.db")
FMDB打開數(shù)據(jù)庫

和數(shù)據(jù)庫建立連接之前,應(yīng)該確保它是打開的,在內(nèi)存不足、禁止開啟、創(chuàng)建數(shù)據(jù)庫的時候會打開失敗。

//OC
if(![db open]){
   //打開數(shù)據(jù)庫失敗
  return;
}
//Swift
if (db?.open())! {
    //打開成功     
}else{
    //打開失敗
}
FMDB執(zhí)行更新

除了SELECT格式的數(shù)據(jù)庫執(zhí)行語句都是更新。包括CREATE,UPDATE,INSERT,ALTER,COMMIT,BEGIN,DETACH,DELETE,DROP,END,EXPLAIN,VACUUM,還有replace語句等等?;旧?,只要你的SQL語句不是以SELECT開頭,都是更新語句。
執(zhí)行更新使用executeUpdate方法,返回一個單一BOOL值,返回值為YES代表執(zhí)行更新成功,返回值為NO表示發(fā)生了一些錯誤。你可以調(diào)用laseErrorMessagelaseErrorCode方法接收更多錯誤信息。

//OC
BOOL  sucess = [db executeUpdate:@"DELETE FROM person WHERE person_id = ?",person.ID];
if sucess {
}else{
  NSLog(@"執(zhí)行出錯了:%@",[db laseErrorMessage])
}
//swift
let sql = String.init(format: "DELETE FROM person WHERE person_id = %d'", person.ID)
guard let sucess = db?. executeUpdate(sql, withArgumentsIn: nil) else{
  debugPrint("\(String(describing: db?.lastErrorMessage()))")
}
if sucess {
}else{
  debugPrint("執(zhí)行出錯了")
}
FMDB執(zhí)行查詢

一個SELECt語句是一個查詢語句并且通過-executrQuery方法執(zhí)行。
執(zhí)行查詢成功返回FMResultSet對象,失敗返回nil。你應(yīng)該使用laseErrorMessagelaseErrorCode方法確定到底為什么查詢失敗。
為了循環(huán)訪問查詢結(jié)果集,你可以使用while()循環(huán).你也需要從一個紀(jì)錄到另一條。在FMDB中,最簡單的辦法是這樣:

//OC
FMResultSet *s = [db executeQuery:@"SELECT *FROM myTable"];
while ([s next]){
//retrieve values for each record
}
//swift
let sql = "SELECT *FROM myTable"
guard let result = db?.executeQuery(checksql, withArgumentsIn: nil) else {
                debugPrint("\(String(describing: db?.lastErrorMessage()))")
                return           
 }
while result.next() {
  //retrieve values for each record
}

通常你在使用查詢結(jié)果的返回值前必須先調(diào)用FMResultSetnext方法,即使你只需要一次,像這樣:

//OC
FMResultSet *s = [db executeQuery:@“SELECT COUNT(*) FROM myTable”];

if ([s next]) {

int totalCount = [s intForColumnIndex:0];

}

FMResultSet 擁有許多方法去獲取適當(dāng)類型的數(shù)據(jù)

intForColumn:

longForColumn:

longLongIntForColumn:

boolForColumn:

doubleForColumn:

stringForColumn:

dateForColumn:

dataForColumn:

dataNoCopyForColumn:

UTF8StringForColumnName:

objectForColumnName:

其中時間date類型取出時使用dateForColumn方法。
這里的每一個方法都有一個對應(yīng)的{type}ForColumnIndex:表達(dá)式?;谧侄卧诮Y(jié)果集中的位置可以被用來獲取數(shù)據(jù),和字段名一一對應(yīng)。
特別的,你在這里不需要close一個FMResultSet,直到結(jié)果集都被釋放或者父數(shù)據(jù)庫關(guān)閉了。

FMDB關(guān)閉(Close)

當(dāng)你完成了數(shù)據(jù)的查詢和更新,你應(yīng)該close這個FMDatabase的連接讓SQLite釋放那些操作過程中占用的資源。

FMDB 事務(wù)(Transactions)

FMDatabase可以通過調(diào)用合適的方法或者執(zhí)行開始和結(jié)束事務(wù)型語句開始并提交一個事務(wù)。多條語句和批量添加你可以使用FMDatabaseexecuteStatements:withResultBlock:去做。
一個多條SQL語句在一個字符串中:

//OC
NSString *sql = @"create table bulktest1 (id integer primary key autoincrement, x text);"

"create table bulktest2 (id integer primary key autoincrement, y text);"

"create table bulktest3 (id integer primary key autoincrement, z text);"

"insert into bulktest1 (x) values ('XXX');"

"insert into bulktest2 (y) values ('YYY');"

"insert into bulktest3 (z) values ('ZZZ');";

success = [db executeStatements:sql];

sql = @"select count(*) as count from bulktest1;"

"select count(*) as count from bulktest2;"

"select count(*) as count from bulktest3;";

success = [self.db executeStatements:sql withResultBlock:^int(NSDictionary *dictionary) {

NSInteger count = [dictionary[@"count"] integerValue];

XCTAssertEqual(count, 1, @"expected one record for dictionary %@", dictionary);

return 0;

}];

OC中對于長字符串的處理,對每一段字符串用雙引號"括起來,可以換行,直到以分號;結(jié)尾,表示字符串結(jié)束。

NSString *aa = @"長字符串的第一段"
    "這是第二段"
    "出現(xiàn)分號則表示字符串結(jié)束"
    ;
FMDB線程安全(FMDatabaseQueue)

在多線程使用同一個FMDatabase的單例是一個壞主意。通常為每一個線程創(chuàng)建FMDatabase對象都是OK的,但千萬不要跨線程使用數(shù)據(jù)庫單例。在多線程同時使用,你可能會遇到異?;蜷W退。
如果需要線程安全,用FMDatabaseQueue替代。
實例化一個FMDatabaseQueue的單例,并且在多線程中使用這個單例,將通過隊列管理,同步執(zhí)行來自多線程的命令。
這里是如何使用,第一步,創(chuàng)建你的隊列。

//OC
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];
//swift       
 let queue = FMDatabaseQueue.init(path: aPath)

然后這樣使用它:

//OC
[queue inDatabase:^(FMDatabase *db) {

[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @1];

[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @2];

[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @3];

FMResultSet *rs = [db executeQuery:@"select * from foo"];

while ([rs next]) {

…

}

}];

擴展:在swift中使用queue的單例的示例代碼:

//swift
class DBHelper: NSObject {
  static let dbQueue:FMDatabaseQueue? = {
        let dbPath = XYWSandBox.getDocumentDirectory() + "/114la.db"
        let dbq = FMDatabaseQueue.init(path: dbPath)
        return dbq
      }()

//類方法,dbQueue是DBHelper單例(static)
class func dbQueryAlldownload() -> [DownloadData]{
  let resultArray = [DownloadData]()
  self.dbQueue?.inDatabase({ (db) in
    let sql = "select * from \(self.downloadsTable) ORDER BY id Desc"
    guard let result = db?.executeQuery(sql, withArgumentsIn: nil) else {
      return
    }
   var resultArray = [DownloadData]()
  while result.next() {
     //獲取數(shù)據(jù)并創(chuàng)建download對象
      resultArray.append(download)
    }
  })
  return resultArray
  }
}

將簡單的多任務(wù)包裝在一個事務(wù)中,使用queueinTransaction方法,當(dāng)

//OC
[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {

[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @1];

[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @2];

[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @3];

if (whoopsSomethingWrongHappened) {

*rollback = YES;

return;

}

// etc…

[db executeUpdate:@"INSERT INTO myTable VALUES (?)", @4];

}];
//swift
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.pointee = true//早版本swift使用rollback.memory = true

    return
}
try db.executeUpdate("INSERT INTO myTable VALUES (?)", values: [4])

} catch {

    rollback.pointee = true//早版本swift使用rollback.memory = true

    print(error)

  }
}

FMDatabaseQueue將在隊列中順序執(zhí)行代碼塊中的任務(wù),因此你可以同時調(diào)用在多線程的多FMDatabaseQueue的方法,在順序排到的時候它們將被執(zhí)行。需要注意的是,在一個queue的block中,不能再次使用queue的block。

實戰(zhàn)篇

實戰(zhàn)場景

我們做一個app,這個app中可以下載網(wǎng)絡(luò)內(nèi)容,我們的下載管理界面中需要顯示一些信息,那么大概需要記錄下載任務(wù)的內(nèi)容有:下載地址,下載文件名,文件大小,已下載大小,創(chuàng)建下載任務(wù)的源網(wǎng)頁地址,創(chuàng)建時間。
我們自己也有個網(wǎng)站,是個說明書大全網(wǎng)站,因為說明書是由我們自己提供的內(nèi)容,所以要能通過已下載的文件,分享這個文件的H5在線閱讀的url地址,在第三方分享的時候要有封面圖的遠(yuǎn)程url地址,并且在已下載文件里這個說明書可以進(jìn)行“反饋”、“舉報”。那么我們就需要知道這個說明書的“id”等信息。因此我們創(chuàng)建一個表單獨記錄這個說明書的信息。

在此功能完成中,需要“下載管理類”、“數(shù)據(jù)庫管理類”、“沙盒文件管理類”相互配合。
其中通過文件的下載url作為關(guān)聯(lián)標(biāo)識符,以此url獲取下載的信息,數(shù)據(jù)的信息,以及文件路徑等信息。
下載管理類主要負(fù)責(zé)文件的下載,暫停,恢復(fù),斷點下載等;沙盒文件管理類主要負(fù)責(zé)下載文件的管理;數(shù)據(jù)庫管理類負(fù)責(zé)記錄下載的信息,以及其他需要持久化的信息。

因此關(guān)于下載后的文件路徑地址,并不在數(shù)據(jù)庫中存儲,而是使用了另外的文件管理類,通過url地址等來獲取相應(yīng)的下載后的路徑。此類不在此文探討范圍,暫且不表。

下載的步驟如下:

  1. 根據(jù)發(fā)起請求的header中的Content-Type檢測打開的是個可下載文件。
  2. 創(chuàng)建下載任務(wù),并在下載數(shù)據(jù)表中記錄這個下載。
  3. 檢查是否是檢查網(wǎng)址是否來自自家網(wǎng)站,如果是,在說明書表中記錄信息。
  4. 文件下載過程更新下載數(shù)據(jù)表中的信息。

顯示步驟如下:

  1. 從下載數(shù)據(jù)表中查詢所有內(nèi)容,或者根據(jù)需求查詢某些內(nèi)容。
  2. 已下載文件的封面為各種格式的默認(rèn)圖,顯示大小、時間等其他信息。
  3. 根據(jù)下載的url從說明書數(shù)據(jù)表中查詢信息。
  4. 如果2沒有找到說明書,不顯示反饋、分享等工具欄,只顯示文件的打開方式。
  5. 如果2有結(jié)果,則顯示此下載關(guān)于說明書的封面圖等其他信息。
實戰(zhàn)代碼

在項目應(yīng)用中,個人推薦所有關(guān)于數(shù)據(jù)庫的操作都放在一個類中。
通過swift的extension,將不同表的操作放在不同的區(qū)域,并且通過文檔注釋//MARK: -的方法,建立快速索引。
這樣就很容易管理數(shù)據(jù)庫,并且此類以外所有地方都不在與數(shù)據(jù)庫交互。

這里我們創(chuàng)建一個數(shù)據(jù)庫管理類DBHelper:

//
//  DBManager.swift
//  Browser
//
//  Created by 西方 on 2018/1/24.
//  Copyright ? 2018年 114la.com. All rights reserved.
//

import UIKit


/// 數(shù)據(jù)庫管理類,可在需要操作數(shù)據(jù)庫的時候,通過擴展的模式添加方法,統(tǒng)一管理便于維護
class DBHelper: NSObject {
    
    
    /// 單例的dbQueue
    static let dbQueue:FMDatabaseQueue? = {
        let dbPath = XYWSandBox.getDocumentDirectory() + "/114la.db"
        let dbq = FMDatabaseQueue.init(path: dbPath)
        return dbq
    }()
    
    /// 下載信息的表
    static let downloadsTable:String = {
       return "downloads"
    }()
    
    
    /// 說明書信息的表
    static let instructionsCacheTable:String = {
       return "instructionsCache"
    }()
    
    
}

//MARK: - 下載表的數(shù)據(jù)庫操作
extension DBHelper {

    /// 創(chuàng)建下載表
    class func createDownloadsTable() {
        self.dbQueue?.inDatabase({ (db) in
            let sql = "create table if not exists \(self.downloadsTable) (id integer primary key autoincrement,url text,weburl text,title text,downloadsize long,filesize long,createtime datetime)"
            
            let result = db?.executeUpdate(sql, withArgumentsIn: nil)
            if result! {
                //表創(chuàng)建成功
                debugPrint("createDownloadsTable sucess")
            }else{
                debugPrint("downloads table create faild!")
                MBProgressHUD.showFailImage("downloads table create faild!")
            }
        })
    }
    
    typealias DownloadResultCompleteHandle = ([DownloadData]) -> ()
    
    /// 查詢所有的下載數(shù)據(jù)
    ///
    /// - Parameter complete: 完成回調(diào)
    class func quryAllDownloads(complete:@escaping DownloadResultCompleteHandle){
        
        self.dbQueue?.inDatabase({ (db) in
            let sql = "select * from \(self.downloadsTable) ORDER BY id Desc"
            guard let result = db?.executeQuery(sql, withArgumentsIn: nil) else {
                
                return
            }
            
            var resultArray = [DownloadData]()
            while result.next() {
                let url = result.string(forColumn: "url") ?? ""
                let webUrl = result.string(forColumn: "webUrl") ?? ""
                let title = result.string(forColumn: "title") ?? "下載出錯"
                let downloadsize = result.double(forColumn: "downloadsize")
                let filesize = result.double(forColumn: "filesize")
                let createtime = result.date(forColumn: "createtime")
                
                let download = DownloadData.init()
                download.url = url
                download.sourceUrl = webUrl
                download.title = title
                download.downloadsize = downloadsize
                download.filesize = filesize
                download.createTime = createtime
                resultArray.append(download)
            }
            complete(resultArray)
            return
        })
    }
    
    
    /// 查詢某個url是否已經(jīng)下載過
    ///
    /// - Parameter url: URL地址
    /// - Returns: 是否下載過
    class func isDownloadExit(_ url:String)->Bool{
        var exit = false
        self.dbQueue?.inDatabase({ (db) in
            let checksql = String.init(format: "select * from %@ where url = '%@'",self.downloadsTable, url.urlEncoded())
            
            guard let exitresult = db?.executeQuery(checksql, withArgumentsIn: nil) else {
                
                debugPrint("\(String(describing: db?.lastErrorMessage()))")
                return
                
            }
            if exitresult.next() {
                debugPrint("下載已存在!")
                exit = true
            }
        })
        
        return exit
    }
    
    /// 添加一個下載任務(wù)
    ///
    /// - Parameters:
    ///   - url: 下載地址
    ///   - title: 任務(wù)的標(biāo)題
    class func dbAddDownload(_ url:String,webUrl:String,title:String){
        
        self.dbQueue?.inDatabase({ (db) in
            
            debugPrint("準(zhǔn)備添加新的下載數(shù)據(jù)!")
            let now = Date.init().timeIntervalSince1970
            
            let sql = String.init(format: "insert into %@ (url,weburl,title,downloadsize,filesize,createtime) values ('%@','%@','%@',0,1,%f)" ,self.downloadsTable,url.urlEncoded(),webUrl.urlEncoded(),title.urlEncoded(),now)
            
            let result = db?.executeUpdate(sql, withArgumentsIn: nil)
            
            if result! {
                debugPrint("dbAddDownload - \(url)")
            }else{
                MBProgressHUD.showFailImage("db add download falid!")
                debugPrint("db add download falid! - \(url) \n \(String(describing: db?.lastErrorMessage()))")
            }
        })
        
    }
    
    
    /// 更新下載的進(jìn)度信息
    ///
    /// - Parameter data: 下載數(shù)據(jù)data
    class func updateDownloadStatus(data:DownloadData) {
        let sql = "update \(self.downloadsTable) set downloadsize = \(data.downloadsize),filesize = \(data.filesize)  where url = '" + data.url.urlEncoded() + "'"
        self.dbQueue?.inDatabase({ (db) in
            let result = db?.executeUpdate(sql, withArgumentsIn: nil)
            if !result! {
                debugPrint("db update download falid!")
            }
        })
    }
    
}


//MARK: - 說明書的數(shù)據(jù)庫操作
extension DBHelper {
    
    /// 創(chuàng)建緩存的說明書表
    class func createInstructionsCacheTable() {
        self.dbQueue?.inDatabase({ (db) in
            let sql = "create table if not exists \(self.instructionsCacheTable) (id integer primary key autoincrement,url text,title text,thumPath text,imgUrl text,tid integer,articletype integer,articeTitle text)"
            let result = db?.executeUpdate(sql, withArgumentsIn: nil)
            if result! {
                //表創(chuàng)建成功
                debugPrint("createInstructionsCacheTable sucess")
            }else{
                debugPrint("instructionsCacheTable create faild!")
                MBProgressHUD.showFailImage("instructionsCacheTable create faild!")
            }
        })
    }
    
    
    /// 添加說明書下載
    ///
    /// - Parameter download: 說明書信息
    class func dbAddInstructionDownload(_ download:InstructionDownloadData){
        self.dbQueue?.inDatabase({ (db) in
            
            let imgPath = download.imgPath ?? ""
            let imgUrl = download.imgUrl ?? ""
            let sql = String.init(format: "insert into %@ (url,title,thumPath,imgUrl,tid,articletype,articeTitle) values ('%@','%@','%@','%@',%d,%d,'%@')", self.instructionsCacheTable,download.url.urlEncoded(),download.title.urlEncoded(),imgPath.urlEncoded(),imgUrl.urlEncoded(),download.tid ?? 0,download.articletype ?? 0,download.articeTitle ?? "unknown")
            
            let result = db?.executeUpdate(sql, withArgumentsIn: nil)
            if result! {
                debugPrint("dbAddInstructionDownload - \(download.url)")
            }else{
                MBProgressHUD.showFailImage("db add instructionCache falid!")
                debugPrint("db add instructionCache falid! - \(download.url)")
            }
            
        })
    }
    
    
    /// 查詢說明書的信息
    ///
    /// - Parameters:
    ///   - url: 下載地主
    ///   - complete: 完成回調(diào)
    class func dbSearchInstructionInfo(by url:String,complete:@escaping (InstructionDownloadData)->()) {
        self.dbQueue?.inDatabase({ (db) in
            let sql = String.init(format: "select * from %@ where url = '%@' ORDER BY id asc",self.instructionsCacheTable,url)
            
            guard let result = db?.executeQuery(sql, withArgumentsIn: nil) else {
                
                return
            }
            
            if result.next(){
                inst.imgPath = result.string(forColumn: "thumPath")
                inst.imgUrl = result.string(forColumn: "imgUrl")
                
                inst.tid = Int(result.int(forColumn: "tid"))
                inst.articletype = Int(result.int(forColumn: "articletype"))
                inst.articeTitle = result.string(forColumn: "articeTitle")
                
                complete(inst)
            }
            result.close()
        })
    }
    
}

這里我使用了閉包進(jìn)行數(shù)據(jù)的回傳,實際上更應(yīng)該通過返回值的方法,這樣能完全隔離數(shù)據(jù)庫操作。由于數(shù)據(jù)操作是同步執(zhí)行,所以不必?fù)?dān)心返回的時候數(shù)據(jù)庫還沒有執(zhí)行完導(dǎo)致數(shù)據(jù)不全或者為空的問題。
我們改寫一個方法:

class func dbSearchInstructionInfo(by url:String,complete:@escaping (InstructionDownloadData)->()) {
        self.dbQueue?.inDatabase({ (db) in
            let sql = String.init(format: "select * from %@ where url = '%@' ORDER BY id asc",self.instructionsCacheTable,url)
            
            guard let result = db?.executeQuery(sql, withArgumentsIn: nil) else {
                
                return
            }
            
            if result.next(){
                inst.imgPath = result.string(forColumn: "thumPath")
                inst.imgUrl = result.string(forColumn: "imgUrl")
                
                inst.tid = Int(result.int(forColumn: "tid"))
                inst.articletype = Int(result.int(forColumn: "articletype"))
                inst.articeTitle = result.string(forColumn: "articeTitle")
                
                complete(inst)
            }
            result.close()
        })
    }

改寫后:

class func dbSearchInstructionInfo(by url:String) -> InstructionDownloadData?{
        
        var data:InstructionDownloadData? = nil
        
        self.dbQueue?.inDatabase({ (db) in
            let sql = String.init(format: "select * from %@ where url = '%@' ORDER BY id asc",self.instructionsCacheTable,url)
            
            guard let result = db?.executeQuery(sql, withArgumentsIn: nil) else {
                
                return
            }
            
            if result.next(){
                let inst = InstructionDownloadData.init()
                inst.imgPath = result.string(forColumn: "thumPath")
                inst.imgUrl = result.string(forColumn: "imgUrl")
                
                inst.tid = Int(result.int(forColumn: "tid"))
                inst.articletype = Int(result.int(forColumn: "articletype"))
                inst.articeTitle = result.string(forColumn: "articeTitle")
                data = inst
            }
            
            result.close()
        })
        return data
    }

從此類之外所有地方,都不在與數(shù)據(jù)庫進(jìn)行交互。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容