fb-script

2016年9月11日 星期日

CoreData with swift 2 筆記 - Migration (2) 「半自動」Mapping File and Migration Policy

上一篇,這次筆記「半自動」模式,計畫如下:
1. 建立一 Use Core Data 專案
2. 建立 Person Entity
3. 在 Person 建立 fullname 欄位
4. 簡單的先在自動產生的 ViewController 中加入一比資料,以空格分開 firstname 和 lastname
5. 首次執行並確定資料新增
6. 刪除新增資料的程式碼
7. 建立一新版本的 Data Model:Model2
8. 在 Model2 中將 fullname 欄位刪除,並加入 firstnamelastname 欄位
9. 建立 Model1 到 Model2 的 Mapping File
10. 建立 fullname 欄位的 Migration Policy File:將 fullname 依照空格拆為 firstnamelastname
11. 將 Migration Policy 設定給 Mapping File
12. 設定 options 啟動 NSMigratePersistentStoresAutomaticallyOption
13. 第二次執行並確認 .sqlite schema 和資料有成功變更

1 ~ 3

專案名稱為:CoreDataManualMappingFile,創建時勾選 Use Core Data 選項

4

新增一筆資料內容為 Michael Jack

import UIKit
import CoreData
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        if let app = UIApplication.sharedApplication().delegate as? AppDelegate {
            let newPerson: NSManagedObject = NSEntityDescription.insertNewObjectForEntityForName("Person", inManagedObjectContext: app.managedObjectContext)
            newPerson.setValue("Michael Jack", forKey: "fullname")
            app.saveContext()
        }
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}
5 ~ 6

AppDelegate.swiftlazy var persistentStoreCoordinator: NSPersistentStoreCoordinator裡將 url print 出來,已確認資料是否建立

let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("SingleViewCoreData.sqlite")
print(url)

print 結果如以下:角括號中為個電腦不同的名稱 /Users/<user>/Library/Developer/CoreSimulator/Devices/<UUID>/data/Containers/Data/Application/<UUID>/Documents/SingleViewCoreData.sqlite

SQLite Browser 開啟該檔案,可以看到目前資料庫的 schema 和實際上的資料。

確認資料有新增後,刪除 4. 中的程式碼。

7 ~ 8

建立一個新的 Data Model Version:DataModel2,將預設 fullname 刪除,並加入 firstnamelastname

9

New File

Source Model

Target Model

檔案名稱: Model1ToModel2

10

新增Person1ToPerson2類別,繼承NSEntityMigrationPolicy並改寫createDestinationInstancesForSourceInstance方法

  • source instance中拿到原始資料
let fullname = sInstance.valueForKey("name")! as! String
  • 使用新的 data model 獲得新 data model 的 instance
let destinationInstance: NSManagedObject = NSEntityDescription.insertNewObjectForEntityForName(mapping.destinationEntityName!, inManagedObjectContext: manager.destinationContext)
  • 解析原始資料,並將結果設定至新的 instance
let fullnameArr = fullname.componentsSeparatedByString(" ")
destinationInstance.setValue(fullnameArr[0], forKey: "firstname")
destinationInstance.setValue(fullnameArr[1], forKey: "lastname")
  • 呼叫 manager 將新舊資料做聯繫
manager.associateSourceInstance(sInstance, withDestinationInstance: destinationInstance, forEntityMapping: mapping)

完整類別程式碼

import CoreData
class Person1ToPerson2: NSEntityMigrationPolicy {
    override func createDestinationInstancesForSourceInstance(sInstance: NSManagedObject, entityMapping mapping: NSEntityMapping, manager: NSMigrationManager) throws {

        let destinationInstance: NSManagedObject = NSEntityDescription.insertNewObjectForEntityForName(mapping.destinationEntityName!, inManagedObjectContext: manager.destinationContext)
        // 新的 data model entity
        let fullname = sInstance.valueForKey("fullname")! as! String
        // 從原始資料庫獲得資料
        let fullnameArr = fullname.componentsSeparatedByString(" ")
        // 依照空格拆成字串陣列
        destinationInstance.setValue(fullnameArr[0], forKey: "firstname")
        destinationInstance.setValue(fullnameArr[1], forKey: "lastname")

        manager.associateSourceInstance(sInstance, withDestinationInstance: destinationInstance, forEntityMapping: mapping)
    }
}

11

選擇 Mapping File –> 點選 PersonToPerson –> 在 Custom Policy 中輸入 CoreDataManualMappingFile.Person1ToPerson2

ps. 在此筆記製作時,如果沒有輸入專案名稱,會找不到 Mapping Policy File

12 ~ 13

先將 Model Version 設定為 Model2

在 AppDelegate 中設定 coordinator.addPersistentStoreWithType 增加啟用 NSMigratePersistentStoresAutomaticallyOption

coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: [
  NSMigratePersistentStoresAutomaticallyOption: true
  ])

因為 coordinator 在 AppDelegate 中設定為 lazy,所以在 ViewDidLoad 中該為直接將 managedObjectContext print 出來,強制執行並 print url

if let app = UIApplication.sharedApplication().delegate as? AppDelegate {
            print(app.managedObjectContext)
        }

執行後再檢視 .sqlite 檔案

可以看到資料庫 schema 已經改為 DataModel2 的欄位配置,並且欄位也依照設定的 Policy 做轉換

結論

半自動模式就是自己定義 Mapping File 和 Migration Policy,這樣想怎麼轉就可以怎麼轉,但是這樣還是有不足的地方,像是如果有 3 個版本的 data model,那就會需要 V1 –> V2、V2 –> V3、V1 –> V3,3 個 轉換結構 ( Mapping File 與 Migration Policy )。因為無法確定用戶端何時更新 APP,所以必須要設想如果用戶端沒有更新到 V2,但是後來更新時,APP Store 上的 data model 版本已經是 V3,那這樣就會造成資料錯誤,所以需要製作 V1 –> V3 的轉換結構。但是這樣又會造成另一個問題,當有 4 個 data model 版本時,那就會需要 6 個 轉換結構,隨著版本越來越多會越來越難維護也越來越難懂。

v:版本數量

n:Mapping File 數量

沒有留言:

張貼留言