顯示廣告
隱藏 ✕
看板 KnucklesNote
作者 Knuckles (站長 那克斯)
標題 [Xcode][Swift3] 使用 CoreData 儲存資料
時間 2017-04-24 Mon. 02:26:38


要儲存資料,讓 APP 強制關掉再打開後資料還在的話

若資料只是單純的 key-value 值的話,可以使用 NSUserDefaults 就好
若是複雜的資料要用到資料庫的話,就要使用 Core Data 了

在 CoreData 底層是使用 SQLite 這個資料庫來儲存資料
只是把 SQL 的指令用物件導向包裝起來


在專案加上 Core Data

在開新專案時,如果有勾選「Use Core Data」
[圖]

會在檔案列表加上一個 XXXX.xcdatamodeld 的檔案
以及在 AppDelegate.swift 加上一堆用來呼叫 Core Data 的程式

但是在 Xcode 8 之後,自動產生的程式使用了 NSPersistentContainer
要在 iOS 10 之後才能使用

如果要支援 iOS 8 的話,參考 StackOverflow
要在 AppDelegate.swift 加上舊版的程式(以下說明使用舊版程式)


假設一開始就沒有勾選「Use Core Data」的話
在 AppDelegate.swift 檔的前面加上
import CoreData

在 func applicationWillTerminate(...){ ... } 之後加上
    // MARK: - Core Data stack

    lazy var applicationDocumentsDirectory: URL = {
        // The directory the application uses to store the Core Data store file.
        let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        return urls[urls.count-1]
    }()

    lazy var managedObjectModel: NSManagedObjectModel = {
        // The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
        let modelURL = Bundle.main.url(forResource: "CoreData", withExtension: "momd")!
        return NSManagedObjectModel(contentsOf: modelURL)!
    }()

    lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
        // The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
        // Create the coordinator and store
        let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
        let url = self.applicationDocumentsDirectory.appendingPathComponent("SingleViewCoreData.sqlite")
        var failureReason = "There was an error creating or loading the application's saved data."
        do {
            try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil)
        } catch {
            // Report any error we got.
            var dict = [String: AnyObject]()
            dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data" as AnyObject?
            dict[NSLocalizedFailureReasonErrorKey] = failureReason as AnyObject?

            dict[NSUnderlyingErrorKey] = error as NSError
            let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
            // Replace this with code to handle the error appropriately.
            // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)")
            abort()
        }

        return coordinator
    }()

    lazy var managedObjectContext: NSManagedObjectContext = {
        // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
        let coordinator = self.persistentStoreCoordinator
        var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
        managedObjectContext.persistentStoreCoordinator = coordinator
        return managedObjectContext
    }()

    // MARK: - Core Data Saving support

    func saveContext () {
        if managedObjectContext.hasChanges {
            do {
                try managedObjectContext.save()
            } catch {
                // Replace this implementation with code to handle the error appropriately.
                // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                let nserror = error as NSError
                NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
                abort()
            }
        }
    }
其中 let modelURL = Bundle.main.url(forResource: "CoreData", withExtension: "momd")!
的 "CoreData" 要改成 XXXX.xcdatamodeld 的檔名 XXXX

新增一個 CoreData.xcdatamodeld 檔,點 command+n
[圖]

Save as: 輸入「CoreData」


使用 Core Data 儲存資料至資料庫

例如我們要加上看板瀏覽記錄的功能

打開檔案列表的 CoreData.xcdatamodeld
點「Add Entity」名稱改為「BoardHistory」
在 BoardHistory 中新增三個 Attributes:bi, name, title
三個 Sttributes 的 Type 分別為:Integer16, String, String
[圖]

在 Core Data 中,Entity 就相當於資料庫的資料表 Table
Attribute 就相當於資料庫的欄位 Field

在右邊的 Data Model 檢視器
[圖]

Codegen 預設為 Class Definition
會自動產生一個 BoardHistory 的類別
只要點上方選單的 Product / Clean 再點 Built 後就可以用了
但是在檔案列表不會出現


在進入看板的程式,新增成員函數 saveBoardHistory()
    // 前面要加上 import CoreData

    func saveBoardHistory() {
        let managedContext = (UIApplication.shared.delegate as! AppDelegate).managedObjectContext
        let entity = NSEntityDescription.entity(forEntityName: "BoardHistory", in: managedContext)!

        // 設定要新增的看板資料
        // 使用自動產生的 BoardHistory 類別
        let insBoard = BoardHistory(entity: entity, insertInto: managedContext)
        insBoard.bi = Int16(self.boardId)
        insBoard.name = self.boardName
        insBoard.title = self.boardTitle

        // 將資料寫入資料庫
        do {
            try managedContext.save()
        } catch let error as NSError {
            print("Could not save. \(error), \(error.userInfo)")
        }
    }
若成員變數 self.boardId 是存字串的話,存入資料庫前要先轉為 Int16


在程式取得 boardId, boardName, boardTitle 的地方,執行
        saveBoardHistory()


使用 Core Data 讀取資料庫

在列出看板瀏覽記錄的程式

加上成員變數 boardHistoryList
    var boardHistoryList = [[String: String]]()
先建立一個空的 [String: String] 陣列

加上成員函數 loadBoardHistory()
    // 前面要加上 import CoreData

    func loadBoardHistoryList() {
        let managedContext = (UIApplication.shared.delegate as! AppDelegate).managedObjectContext

        let fetchRequest = NSFetchRequest<BoardHistory>(entityName: "BoardHistory")
        var fetchResult = [BoardHistory]()
        do {
            fetchResult = try managedContext.fetch(fetchRequest).reversed()
        } catch let error as NSError {
            print("Could not fetch. \(error), \(error.userInfo)")
        }

        self.boardHistoryList.removeAll()
        for board in fetchResult {
            if let boardName = board.name, let boardTitle = board.title {
                self.boardHistoryList.append(["name": boardName, "title": boardTitle])
            }
        }
    }
將資料庫中 BoardHistory 儲存的資料取出來
存在成員變數 boardHistoryList

取得 fetchResult 時,加上 reversed() 將順序反轉
讓新存進去的值會排在前面

不能直接將取得的 BoardHistory 陣列存在成員變數
因為 BoardHistory 的值可能會其他地方存取 CoreData 時被改掉
所以要將 BoardHistory 陣列裡的值取出來,
另外存成一個 [String: String] 陣列才行


在 viewDidLoad() 中加上
        loadBoardHistoryList()


修改 Table view data source 函數

在 tableView(_:numberOfRowsInsection:) 設定列表有幾個 row
        return boardHistoryList.count

在 tableView(_:cellForRowAt:) 設定要顯示的值
        let board = self.boardHistoryList[indexPath.row]
        let boardName = board["name"]!
        let boardTitle = board["title"]!

        cell.textLabel?.text = "\(boardName) \(boardTitle)"


使用 Core Data 刪除資料庫某幾筆資料

現在可以記錄並列出瀏覽過的看板了
但是若重覆進入同一個看板,瀏覽記錄就會顯示重覆的資料
所以要在加入新資料前,先把之前存過的記錄刪除

修改之前加上的 saveBoardHistory()
在 let insBoard = ... 的前面加上
        //先刪除這個看板之前的瀏覽記錄
        let fetchRequest = NSFetchRequest<BoardHistory>(entityName: "BoardHistory")
        fetchRequest.predicate = NSPredicate(formate: "name == %@", self.boardName)
        if let fetchResult = try? managedContext.fetch(fetchRequest) {
            for delBoard in fetchResult {
                managedContext.delete(delBoard)
            }
        }
使用 fetchRequest 的 predicate 過濾資料
例如我們要找出板名為 self.boardName 的資料

使用 if let fetchResult = try? managedContext.fetch(fetchRequest) {
確保有找到資料的話才執行刪除

for delBoard in fetchResult {
找到的資料可能有好幾筆,所以要用迴圈來刪除

managedContext.delete(delBoard)
呼叫 managedContext 的 delete(),傳入讀取出來的 BoardHistory 物件即可刪除

後面還要記得加上 managedContext.save() 才會儲存變更


參考
RayWenderlich Getting Started with Core Data Tutorial

--
※ 作者: Knuckles 時間: 2017-04-24 02:26:38
※ 編輯: Knuckles 時間: 2017-05-07 20:31:59
※ 看板: KnucklesNote 文章推薦值: 0 目前人氣: 0 累積人氣: 2209 
分享網址: 複製 已複製
r)回覆 e)編輯 d)刪除 M)收藏 ^x)轉錄 同主題: =)首篇 [)上篇 ])下篇