iOS之數據持久化進階(plist、SQLite、CoreData)

簡介

  • 持久化方式就是數據存儲方式.iOS支持本地存儲和雲端存儲,而本地存儲主要涉及如下三種機制:
  • 屬性列表:集合對象可以讀寫到屬性列表中;
  • SQLite數據庫:SQLite是一個開源嵌入式關係型數據庫;
  • CoreData:是一種對象關係映射技術(ORM),本質上也是通過SQLite存儲.
  • 屬性列表文件一般用於存儲少量數據,Foundation框架中的集合對象都有對應的方法讀寫屬性列表文件了;SQLite數據庫和CoreData一般用於有幾個簡單表關係的大量數據情況。如果是複雜表關係而且數據量很大,我們應該考慮把數據放在遠程服務器上。
  • 分別用屬性列表,SQLite,CoreData來做一個類似備忘錄的增刪改查,效果如下。
    在這裏插入圖片描述

屬性列表

  • 數據列表本質是XML文件,Foundation框架中的數組和字典等都可以與屬性列表文件互相轉換。

    1. + (nullable NSArray<ObjectType> *)arrayWithContentsOfFile:(NSString *)path;:類工廠方法,從屬性列表讀取數據,創建NSArray對象,Swift沒有對應的構造函數。
    2. - (nullable NSArray<ObjectType> *)initWithContentsOfFile:(NSString *)path; :構造函數,從屬性列表讀取數據,創建NSArray對象;Swift語言表示爲public convenience init?(contentsOfFile path: String)
    3. - (BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile;:將NSArray或者NSDictionary對象寫入到屬性列表中,參數path是屬性類表文件路徑,useAuxiliaryFile表示是否使用輔助文件,如果設置爲YES則先寫入輔助文件,然後將輔助文件重寫命名爲目標文件;如果爲NO,則直接寫入目標文件,返回的布爾值爲YES則邊上寫入成功,否寫入失敗。對應的Swift方法open func write(toFile path: String, atomically useAuxiliaryFile: Bool) -> Bool
    4. + (nullable NSDictionary<KeyType, ObjectType> *)dictionaryWithContentsOfFile:(NSString *)path;類工廠方法,從屬性列表讀取數據,創建NSDictionary對象,Swift沒有對應的構造函數。
    5. - (nullable NSDictionary<KeyType, ObjectType> *)initWithContentsOfFile:(NSString *)path;:構造函數,從屬性列表讀取數據,創建NSDictionary對象;Swift語言表示爲public convenience init?(contentsOfFile path: String)
  • 關鍵代碼類

數據模型類

import Foundation
//數據模型
public class Note {
    var date: Date
    var content: String
    init(date: Date,content: String) {
        self.date = date
        self.content = content
    }
    
    init() {
        self.date = Date()
        self.content = ""
    }
}

數據操作類

import Foundation
//用來訪問屬性列表文件的工具類
public class NoteDAO {
   private var dateFormatter = DateFormatter()
   //屬性類表訪問路勁
   private var plistFileParh:String!
   public static let shareInstance: NoteDAO = {
       let instance = NoteDAO()
       //屬性列表路勁
       instance.plistFileParh = instance.applicationDocumentsDirectoryFile
       instance.dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
       //初始化屬性列表文件
       instance.createEditableCopyOfDatabaseIfNeeded()
       return instance
   }()
   private func createEditableCopyOfDatabaseIfNeeded(){
       let fileManager = FileManager.default
       //判斷屬性列表是否已經存在
       let dbexits = fileManager.fileExists(atPath: self.plistFileParh)
       if (!dbexits){
           //獲取Bundle,通過該對象可以訪問當前程序的資源目錄,而下面的構造方法可以避免當前文件拉到別的項目用因爲項目名稱不對而訪問不到
           let frameworkBundle = Bundle(for: NoteDAO.self)
//            let defaultDBPath = Bundle.main.path(forResource:"NotesList", ofType: "plist")
           let frameworkBundlePath = frameworkBundle.resourcePath as NSString?
           let defaultDBPath = frameworkBundlePath!.appendingPathComponent("NotesList.plist")
           do{
               //實現文件複製
               try fileManager.copyItem(atPath: defaultDBPath, toPath: self.plistFileParh)
               
           }catch{
               let nserror = error as NSError
               print("數據保存錯誤:\(nserror.localizedDescription)")
               assert(false, "錯誤寫入文件")
           }
       }
   }
   let applicationDocumentsDirectoryFile: String = {
       let documentDirectory: NSArray = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) as NSArray
       let basePath = documentDirectory.firstObject as!String
       let path = basePath + "/" + "NotesList.plist"
       return path
   }()
   
   //插入Note數據模型到屬性列表中
   public func create(_ mode: Note) -> Int{
       let array  = NSMutableArray(contentsOfFile: self.plistFileParh)!
       let strDate = self.dateFormatter.string(from: mode.date as Date)
       let dict = NSDictionary(objects: [strDate, mode.content], forKeys: ["date" as NSCopying,"content" as NSCopying])
       array.add(dict)
       array.write(toFile: self.plistFileParh, atomically: true)
       return array.count - 1
   }
   //從屬性列表中刪除數據
   public func remove(_ model : Note) -> Int{
       let array = NSMutableArray(contentsOfFile: self.plistFileParh)!
       var index = 0
       for item in array {
           let dict = item as!NSDictionary
           let strDate = dict.value(forKey: "date")as!String
           let date = dateFormatter.date(from: strDate)!
           index += 1
           //比較日期主鍵是否相等
           if date == model.date as Date{
               array.remove(dict)
               array.write(toFile: self.plistFileParh, atomically: true)
               return index
           }
       }
       return 0
   }
   //修改屬性列表中的數據
   public func modify(_ model: Note) -> Int{
       let array = NSMutableArray(contentsOfFile: self.plistFileParh)!
       var index = 0
       for item in array {
           let dict = item as!NSDictionary
           let strDate = dict.value(forKey: "date")as!String
           let date = dateFormatter.date(from: strDate)!
           index += 1
           //比較日期主鍵是否相等
           if date == model.date as Date{
               dict.setValue(model.content, forKey: "content")
               array.write(toFile: self.plistFileParh, atomically: true)
               return index
           }
       }
       return 0
   }
   //查詢屬性列表中所有數據模型數組
   public func findAll() -> NSArray{
       let array = NSMutableArray(contentsOfFile: self.plistFileParh)!
       let listData = NSMutableArray()
       for item in array {
           let dict = item as! NSDictionary
           let strDate = dict.value(forKey: "date")as!String
           let date = dateFormatter.date(from: strDate)!
           let content = dict.value(forKey: "content") as! String
           let note = Note(date: date, content: content)
           listData.add(note)
       }
       return listData.copy() as! NSArray
   }
   //按照時間主鍵查詢數據
   public func findById(_ model: Note) -> Note?{
       let array = NSMutableArray(contentsOfFile: self.plistFileParh)!
       for item in array {
           let dict = item as! NSDictionary
           let strDate = dict.value(forKey: "date")as!String
           let date = dateFormatter.date(from: strDate)!
           let content = dict.value(forKey: "content") as! String
           if date == model.date as Date{
               let note = Note(date: date, content: content)
               return note
           }
       }
       return nil
   }
}

SQLite數據庫

  • SQLite是嵌入式系統使用的關係數據庫,目前主流的版本是SQLite 3。採用C語言編寫,具有可移植性強、可靠性高、小而易用的特點。SQLite 運行時與使用它的應用程序之間共用相同的進程空間,而不是單獨的兩個進程。
  • SQLite提供了對SQL-92標準的支持,支持多表、索引、事物、視圖和觸發。SQLite是無數據類型數據庫。
  • 雖然SQLite可以忽略數據類型,但從編程規範上講,我們還是應該在創建表的時候指定數據類型。常見的數據類型有:
    INTEGER:有符號整數類型
    REAL:浮點類型
    TEXT:字符串類型
    BLOB:二進制大對象類型,能夠存放任何二進制數據
    SQLite沒有布爾類型,可用0和1代替,也沒有日期和時間類型。

iOS項目中使用SQLite

  • 添加SQLite3庫:
    選擇工程中TARGETS -> Build Phases -> Link Binary With Libraries -> +選擇libsqlite3.0.tbdlibsqlite3.tbd添加
    添加SQLite數據庫
  • 配置Swift環境:
    如果我們採用Swift語言開發,則會更加複雜。這是因爲SQLite API是基於C語言的,Swift要想調用C語言API,則需要橋接頭文件。具體可參考文章:Swift和OC混編。在橋接文件中導入#import "sqlite3.h"
  • 常見sqlite操作函數
OpaquePointer: *db,數據庫句柄,跟文件句柄FIFL類似,這裏是sqlite3指針;

sqlite3_stmt: *stmt,相當於ODBC的Command對象,用於保存編譯好的SQL語句;

sqlite3_open(): 打開數據庫,沒有數據庫時創建;

sqlite3_exec(): 執行非查詢的SQL語句;

sqlite3_step(): 在調用sqlite3_prepare後,使用這個函數在記錄集中移動;

sqlite3_close():關閉數據庫文件;

sqlite3_column_text():取text類型的數據;

sqlite3_column_blob():取blob類型的數據;

sqlite3_column_int():取int類型的數據;
  • 創建數據庫,分爲3步:
    1. 使用sqlite3_open函數打開數據庫
    2. 使用sqlite3_exec函數執行Create Table語句,創建數據表
    3. 使用sqlite3_close函數釋放資源
import Foundation
//數據庫名稱
let DBFILE_NAME = "NoteList.sqlite3"
class NoteDAO_SQLite3{
   //如果指向的類型沒有完全定義或不能在 Swift 中表示,這種指針將會被轉換爲 COpaquePointer (在 Swift 3.0 中,將會簡化爲 OpaquePointer ),一種沒有類型的指針,特別是只包含一些位(bits)的結構體。 COpaquePointer 指向的值不能被直接訪問,指針變量首先需要轉換才能使用。
   private var db: OpaquePointer? = nil
   private var dateFormatter = DateFormatter()
   //數據庫路徑
   private var dbFilePath:String!
   
   public static let shareInstance: NoteDAO_SQLite3 = {
       let instance = NoteDAO_SQLite3()
       //數據庫路勁
       instance.dbFilePath = instance.applicationDocumentsDirectoryFile
       instance.dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
       //初始化數據庫
       instance.createEditableCopyOfDatabaseIfNeeded()
       return instance
   }()
   //數據庫路勁
   let applicationDocumentsDirectoryFile: String = {
       let documentDirectory: NSArray = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) as NSArray
       let basePath = documentDirectory.firstObject as!String
       let path = basePath + "/" + DBFILE_NAME
       return path
   }()
   //初始化文件
   private func createEditableCopyOfDatabaseIfNeeded(){
       let fileManager = FileManager.default
       //判斷數據庫是否已經存在
       let dbexits = fileManager.fileExists(atPath: self.dbFilePath)
       //如果已經數據庫已經存在了就直接返回,不再創建了
       if dbexits{
           return
       }
       //轉化爲c語言字符串
       let cpath = self.dbFilePath.cString(using: String.Encoding.utf8)
       if sqlite3_open(cpath!, &db) != SQLITE_OK{
           print("打開數據庫失敗")
       }else{
           //數據庫打開成功則創建表,並設置cdate爲主鍵,content關鍵字爲內容
           let sql = "CREATE TABLE IF NOT EXISTS Note(cdate TEXT PRIMARY KEY,content TEXT)"
           let cSql = sql.cString(using: String.Encoding.utf8)
           //執行sql語句
           if (sqlite3_exec(db, cSql!, nil, nil, nil) != SQLITE_OK){
               print("建表失敗")
           }
       }
       sqlite3_close(db)
   }
}
  • 查詢數據,主要有一下步驟:
    1. 使用sqlite3_open函數打開數據庫
    2. 使用sqlite3_prepare_v2函數預處理SQL語句
    3. 使用sqlite3_bind_text函數綁定參數
    4. 使用sqlite3_step函數執行SQL語句,變量結果子集
    5. 使用sqlite3_column_text等函數提取字段數據
    6. 使用sqlite3_finalizesqlite3_close函數釋放資源關閉數據庫
 //查詢數據
extension NoteDAO_SQLite3{
   //按照主鍵查詢某一個數據
   public func findById(_ model: Note) -> Note?{
       //獲取數據庫路徑C語言
       let cpath = self.dbFilePath.cString(using: String.Encoding.utf8)
       //打開數據庫
       if sqlite3_open(cpath!, &db) != SQLITE_OK{
           print("數據庫打開失敗")
       }else{
           //where條件查詢語句
           let sql = "SELECT cdate,content FROM Note where cdate = ?"
           let cSql = sql.cString(using: String.Encoding.utf8)
           var statement: OpaquePointer? = nil
           //預處理過程
           if sqlite3_prepare_v2(db, cSql!, -1, &statement, nil) == SQLITE_OK{
               //準備參數
               let strDate = self.dateFormatter.string(from: model.date as Date)
               let cDate = strDate.cString(using: String.Encoding.utf8)
               //綁定參數
               sqlite3_bind_text(statement, 1, cDate!, -1, nil)
               //執行查詢
               if sqlite3_step(statement) == SQLITE_ROW{
                   //還有其他行沒有遍歷
                   let note = Note()
                   //讀取字符串字段
                   if let strDate = getColumValue(index: 0, stmt: statement!){
                       //獲取日期
                       let date: Date = self.dateFormatter.date(from: strDate)!
                       //給模型賦值
                       note.date = date
                   }
                   if let strContent = getColumValue(index: 1, stmt: statement!){
                       note.content = strContent
                   }
                   //釋放資源
                   sqlite3_finalize(statement)
                   //關閉數據庫
                   sqlite3_close(db)
                   return note
               }
           }
           //釋放資源
           sqlite3_finalize(statement)
       }
       //關閉數據庫
       sqlite3_close(db)
       return nil
   }
   //獲取字段數據
   private func getColumValue(index:CInt, stmt:OpaquePointer)->String?{
       //UnsafeMutableRawPointer是指任意類型的c指針
       if let prt = UnsafeMutableRawPointer.init(mutating: sqlite3_column_text(stmt, index)){
           //將任意類型的c指針綁定到Char類型指針
           let uptr = prt.bindMemory(to:CChar.self,capacity:0)
           //將指針所指類容創建成String類型
           let txt = String(validatingUTF8: uptr)
           return txt
       }
       return nil
   }
   
   //查詢所有數據
   public func findAll() -> Array<Any>{
       let cpath = self.dbFilePath.cString(using: String.Encoding.utf8)
       var listData = Array<Any>()
       if sqlite3_open(cpath!, &db) != SQLITE_OK{
           print("數據庫打開失敗")
       }else{
           //插入數據SQL語句
           let sql = "SELECT cdate ,content FRPM Note"
           let cSql = sql.cString(using: String.Encoding.utf8)
           var statement: OpaquePointer? = nil
           //預處理過程
           if sqlite3_prepare_v2(db, cSql!, -1, &statement, nil) == SQLITE_OK{
               //執行查詢語句
               while sqlite3_step(statement) == SQLITE_ROW{//遍歷查詢結果集, SQLITE_ROW表示結果集中海油數據
                   let note = Note()
                   if let strDate = getColumValue(index: 0, stmt: statement!){
                       let date: Date = self.dateFormatter.date(from: strDate)!
                       note.date = date
                   }
                   if let strContent = getColumValue(index: 1, stmt: statement!){
                       note.content = strContent
                   }
                   listData.append(note)
               }
           }
           sqlite3_finalize(statement)
       }
       sqlite3_close(db)
       return listData
   }
}
  • 修改數據,主要有一下步驟:
    1. 使用sqlite3_open函數打開數據庫
    2. 使用sqlite3_prepare_v2函數預處理SQL語句
    3. 使用sqlite3_bind_text函數綁定參數
    4. 使用sqlite3_step函數執行SQL語句
    5. 使用sqlite3_finalizesqlite3_close函數釋放資源關閉數據庫
   //修改數據
extension NoteDAO_SQLite3{
    //添加數據
    public func create(_ model: Note){
        //獲取數據庫路徑C語言
        let cpath = self.dbFilePath.cString(using: String.Encoding.utf8)
        if sqlite3_open(cpath!, &db) != SQLITE_OK{
            print("數據庫打開失敗")
        }else{
            //插入數據SQL語句
            let sql = "INSERT OR REPLACE INTO note (cdate, content) VALUE(?,?);"
            let cSql = sql.cString(using: String.Encoding.utf8)
            var statement: OpaquePointer? = nil
            //預處理過程
            if sqlite3_prepare_v2(db, cSql!, -1, &statement, nil) == SQLITE_OK{
                //準備參數
                let strDate = self.dateFormatter.string(from: model.date as Date)
                let cDate = strDate.cString(using: String.Encoding.utf8)
                let cConetent = model.content.cString(using: String.Encoding.utf8)
                //綁定參數
                sqlite3_bind_text(statement, 1, cDate!, -1, nil)
                sqlite3_bind_text(statement, 2, cConetent!, -1, nil)
                //執行插入數據SQL語句
                if sqlite3_step(statement) != SQLITE_DONE{//判斷是否執行完成
                    print("插入數據失敗")
                }
            }
            sqlite3_finalize(statement)
        }
        sqlite3_close(db)
    }
    //刪除數據
    public func remove(_ model: Note){
            //獲取數據庫路徑C語言
            let cpath = self.dbFilePath.cString(using: String.Encoding.utf8)
            if sqlite3_open(cpath!, &db) != SQLITE_OK{
                print("數據庫打開失敗")
            }else{
                //根據日期時間主鍵刪除數據SQL語句
                let sql = "DELETE from note where cdate = ?"
                let cSql = sql.cString(using: String.Encoding.utf8)
                var statement: OpaquePointer? = nil
                //預處理過程
                if sqlite3_prepare_v2(db, cSql!, -1, &statement, nil) == SQLITE_OK{
                    //準備參數
                    let strDate = self.dateFormatter.string(from: model.date as Date)
                    let cDate = strDate.cString(using: String.Encoding.utf8)
                    //綁定參數
                    sqlite3_bind_text(statement, 1, cDate!, -1, nil)
                    //執行SQL語句
                    if sqlite3_step(statement) != SQLITE_DONE{//判斷是否執行完成
                        print("插入數據失敗")
                    }
                }
                sqlite3_finalize(statement)
            }
        sqlite3_close(db)
    }
    
    //修改數據
    public func modify(_ model: Note){
        //獲取數據庫路徑C語言
        let cpath = self.dbFilePath.cString(using: String.Encoding.utf8)
        if sqlite3_open(cpath!, &db) != SQLITE_OK{
            print("數據庫打開失敗")
        }else{
            //根據日期時間主鍵跟新內容的SQL語句
            let sql = "UPDATE note set content =? where cdate =?"
            let cSql = sql.cString(using: String.Encoding.utf8)
            var statement: OpaquePointer? = nil
            //預處理過程
            if sqlite3_prepare_v2(db, cSql!, -1, &statement, nil) == SQLITE_OK{
                //準備參數
                let strDate = self.dateFormatter.string(from: model.date as Date)
                let cDate = strDate.cString(using: String.Encoding.utf8)
                let cConetent = model.content.cString(using: String.Encoding.utf8)
                //綁定參數
                sqlite3_bind_text(statement, 1, cDate!, -1, nil)
                sqlite3_bind_text(statement, 2, cConetent!, -1, nil)
                //執行SQL語句
                if sqlite3_step(statement) != SQLITE_DONE{//判斷是否執行完成
                    print("插入數據失敗")
                }
            }
            sqlite3_finalize(statement)
        }
        sqlite3_close(db)
    }
}

CoreData

  • CoreData出現在iOS 3中,是蘋果爲macOS和iOS系統應用開發提供的對象關係映射技術(ORM)。它基於高級數據持久化API,其底層最終是SQLite數據庫、二進制文件和內存數據保存,使開發人員不用再關係數據儲存細節問題,不用再使用SQL語句,不用面對SQLite的C語言函數。
  • 實體在關係模型中代表表的一條數據,該表描述了實體的結構有哪些屬性和關係。實體在對象模型中代表類的一個對象,類描述了實體的結構,實體是類的對象。因此表是與類對應的概念,記錄是與對象對應的概念
    關係型數據庫

CoreData的具體使用步驟

1. 添加CoreData:

  1. 創建項目時,Xcode有兩種工程模板(Master-Detail Application和Single View Application)可以直添加CoreData支持。勾選中Use Core Data即可。
    CoreData
    創建完後會自動生成工程名.xcdatamodeld文件(它是模型文件可利用它可視化地設計數據庫,生成實體類代碼和SQLite數據文件),並在AppDelegate生成創建Core Data棧和數據保存的方法代碼(iOS以前要自己去手寫),
import UIKit
import CoreData

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
// MARK: - Core Data stack  創建Core Data棧
    //NSPersistentContainer 是iOS 10 新增加的類,稱爲持久化容器,用於簡化之前的Core Data棧創建過程;iOS 10之前需要我們手動創建Core Data棧
    //storeDescription 是NSPersistentStoreDescription類型用來描述配置信息
    lazy var persistentContainer: NSPersistentContainer = {
        //參數name是容器的名字,它也是放到MainBundle資源目錄中模型文件的名字
        let container = NSPersistentContainer(name: "Core_Data")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()

    // MARK: - Core Data Saving support   Core Data數據保存方法
    func saveContext () {
        let context = persistentContainer.viewContext
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                let nserror = error as NSError
                fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
            }
        }
    }
}
  1. 在已有項目中創建CoreData。
    File->New->File->Core Data->Data Model
    Data Model
  • Core Data 實現數據持久化是通過Core Data棧。它有一個或多個被管理的對象上下文,連接到一個持久化存儲協調器,一個持久化存儲協調器連接到一個或多個持久化對象存儲,持久化對象存儲與底層存儲文件關聯。一個持久化存儲協調器也可以管理多個被管理對象模型。一個持久化存儲協調器就意味着一個Core Data棧,我們可以實現數據的增、刪、改、查。
    Core Data棧
  1. 被管理對象上下文(MOC):在被管理對象上下文中可以查找、刪除、和插入對象,然後通過棧同步到持久化對象存儲。對應類是NSManagedObjectContext
  2. 持久化存儲協調器(PSC):在持久化對象存儲智商提供一個接口,我們可以把他理解成數據庫連接。對相應類是NSPersistentyStoreCoordinator
  3. 持久化對象存儲(POS):執行所有底層的從對象到數據的轉換,並負責打開和關閉數據文件。有3種持久化實現方式:SQLite、二進制文件和內存形式。
  4. 被管理對象模型(MOM): 是系統中的實體,與數據中的表等對象對應。對應類是NSManagedObjectModel

2. 添加生成實體

  • 添加實體:選中.xcdatamodeld文件,添加實體(表),修改實體名(表名),添加字段(屬性)。
    添加實體
  • 生成實體:先綁定Core Data棧管理的對象。選擇Editor -> Create NSManagedObject Subclass...,然後選擇對應的模型文件,選擇對應的實體表文件。會生成兩個swift文件:NoteManagedObject+CoreDataClass.swift(定義了NoteManagedObject類,它是被Core Data管理的對象),NoteManagedObject+CoreDataProperties.swift(是NoteManagedObject的擴展)
    綁定管理對象
    在這裏插入圖片描述
    選擇模型文件
    選擇表文件
    注意1:可能會產生錯誤Invalid redeclaration of 'NoteManagedObject'
    在這裏插入圖片描述
    解決方案:在Create NSManagedObject Subclass...之前要設置CodegenManua/None
    解決方案
    注意2:默認情況下,模型文件生成的代碼都是Swift語言,如果OC項目想生成OC語言,必須選中工程名.xcdatamodeld然後設置Code Generation -> LanguageObjective-C,這裏本身是Swift示例則不用修改。
    模型文件生成的代碼都是Swift語言

3. 創建CoreData棧DAO

//CoreDataDAO類似於Xcode自動生成的CoreData棧代碼

import Foundation
import CoreData
//定義一個操作CoreData的類
class CoreDataDAO: NSObject {
    //懶加載持久化存儲器屬性
    lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "Model")
        container.loadPersistentStores { (storDes, error) in
            if let error = error as NSError?{
                print("持久化存儲器錯誤:",error.localizedDescription)
            }
        }
        return container
    }()
    
    //保存數據
    func  saveContext(){
        let context = persistentContainer.viewContext
        if context.hasChanges {
            do{
                try context.save()
            }catch{
                let nserror = error as NSError
                print("數據保存錯誤:",nserror.localizedDescription)
            }
        }
    }
    
}

4. 創建CoreDataDAO子類,添加NoteManagedObject被管理的實體類

  • NoteCoreDataDAO繼承NoteCoreDataDAO,對數據的增刪改查都在這裏進行。
import UIKit
import CoreData
//繼承於CoreDataDAO
class NoteCoreDataDAO: CoreDataDAO {
//    var context : NSManagedObjectContext!
    public static let shareInstance: NoteCoreDataDAO = {
        let instance = NoteCoreDataDAO()
        //1.創建模型對象
        //獲取模型路徑
        let url = Bundle.main.url(forResource: "Model", withExtension: "momd")
        //根據模型文件創建模型對象
        let model = NSManagedObjectModel(contentsOf: url!)
        //2.創建持久化存儲助理:數據庫
        //利用模型對象創建助理對象
        let store = NSPersistentStoreCoordinator(managedObjectModel: model!)
        let documentDirectory: NSArray = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) as NSArray
        let basePath = documentDirectory.firstObject as!String
        //數據庫路徑
        let dbFilePath = basePath + "/" + "coreData.sqlite"
        let dbUrl = NSURL(fileURLWithPath: dbFilePath)
        //設置數據庫相關信息 添加一個持久化存儲庫並設置存儲類型和路徑,NSSQLiteStoreType:SQLite作爲存儲庫
        do{
            try store.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: dbUrl as URL, options: nil)
        }catch{
            let nserror = error as NSError
            print("數據保存錯誤:",nserror.localizedDescription)
        }
        
//        instance.context = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.mainQueueConcurrencyType)
//        //關聯持久化助理
//        instance.context.persistentStoreCoordinator = store
        return instance
    }()
}
//查詢數據
extension NoteCoreDataDAO {
    //查詢所有數據(無條件查詢)
    public func findAll() -> NSMutableArray {
        let context = persistentContainer.viewContext
        //NSEntityDescription是實體關聯的描述類,通過制定實體名字獲得NSEntityDescription實例對象
        let entity = NSEntityDescription.entity(forEntityName: "Note", in: context)
        //NSFetchRequest是數據提取請求類,用於查詢
        let fetchRequest = NSFetchRequest<NSFetchRequestResult>()
        //把實體描述設置到請求對象
        fetchRequest.entity = entity
        //NSSortDescriptor是排序描述類,可以指定排序字段和排序方式
        let sortDes = NSSortDescriptor(key: "date", ascending: true)
        //把排序描述對象的數組賦值給請求對象中
        fetchRequest.sortDescriptors = [sortDes]
        let resListData = NSMutableArray()
        do{
            //根據請求對象執行查詢,返回數組集合,數據集合中放置的是被管理的NoteManagedObject實例對象
            let listData = try context.fetch(fetchRequest)
            for item in listData {
                let mo = item as! NoteManagedObject
                //轉換成Note實例對象
                let note = Note(date: mo.date! as Date, content:mo.content!)
                resListData.add(note)
            }
        }catch{
            print("查詢數據失敗")
        }
        return resListData
    }
    
    //查詢單個數據(有條件查詢)
    public func findById(_ model: Note) -> Note?{
        let context =  persistentContainer.viewContext
        //NSEntityDescription是實體關聯的描述類,通過制定實體名字獲得NSEntityDescription實例對象
        let entity = NSEntityDescription.entity(forEntityName: "Note", in: context)
        //NSFetchRequest是數據提取請求類,用於查詢
        let fetchRequest = NSFetchRequest<NSFetchRequestResult>()
        //把實體描述設置到請求對象
        fetchRequest.entity = entity
        //NSPredicate是謂詞,用於過濾對象
        fetchRequest.predicate = NSPredicate(format: "date = %@", model.date as CVarArg)
        do{
            let listData = try context.fetch(fetchRequest)
            if listData.count > 0 {
                let mo = listData[0] as! NoteManagedObject
                let note = Note(date: mo.date! as Date, content: mo.content!)
                return note
            }
            
        }catch{
            print("查詢數據失敗")
        }
         return nil
    }
    
}
//修改數據(增刪改)
extension NoteCoreDataDAO{
    //插入數據
    public func create(_ model: Note){
        let context =  persistentContainer.viewContext
        //創建一個被管理的Note實例對象,返回值類型是NSManagedObject,本例中是NoteManagedObject,繼承於NSManagedObject
        let note = NSEntityDescription.insertNewObject(forEntityName: "Note", into: context)as! NoteManagedObject
        note.date = model.date as NSDate
        note.content = model.content
       //保存數據
        self.saveContext()
    }
    // 刪除數據
    public func remove(_ model: Note){
        let context =  persistentContainer.viewContext
        let entity = NSEntityDescription.entity(forEntityName: "Note", in: context)
        let fetchRequest = NSFetchRequest<NSFetchRequestResult>()
        fetchRequest.entity = entity
        fetchRequest.predicate = NSPredicate(format: "date = %@", model.date as CVarArg)
        do {
            let listData =  try context.fetch(fetchRequest)
            if listData.count > 0{
                let note = listData[0] as! NSManagedObject
                //刪除實體
                context.delete(note)
                self.saveContext()
            }
        } catch {
            print("刪除數據失敗")
        }
    }
    //修改數據
    public func modify(_ model: Note){
        let context =  persistentContainer.viewContext
        let entity = NSEntityDescription.entity(forEntityName: "Note", in: context)
        let fetchRequest = NSFetchRequest<NSFetchRequestResult>()
        fetchRequest.entity = entity
        fetchRequest.predicate = NSPredicate(format: "date = %@", model.date as CVarArg)
        do {
            let listData =  try context.fetch(fetchRequest)
            if listData.count > 0{
                let note = listData[0] as! NoteManagedObject
                note.content = model.content
                self.saveContext()
            }
        } catch {
            print("修改數據失敗")
        }
    }
}
類名 說明
CoreDataDAO DAO基類
NoteCoreDataDAO NoteCoreDataDAO類用於對數據的增刪改查
Note 未被管理的實體類
NoteManagedObject 被管理的實體類

NoteManagedObject對象必須被嚴格限定值持久層中使用,不能出現在表示層和業務邏輯層,而Note對象則可以出現在表示層、業務邏輯層和持久層中。因此在持久層中將CoreData棧查詢出NoteManagedObject數據轉換爲Note對象,返回給表示層和業務邏輯層。這個工作看起來比較麻煩,但基於CoreData實現的分層架構必須遵守這個規範。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章