Swift 定制 Core Data 遷移

在這里插入圖片描述

在這里插入圖片描述

文章目錄

    • 前言
    • 什么是 Core Data 遷移?
    • 示例
    • 更新模型
    • 創建一個新的模型版本
    • 創建映射模型
    • 編寫自定義遷移策略
    • 總結

前言

隨著應用程序和用戶群的增長,你需要添加新功能,刪除其他功能,并改變應用程序的工作方式。這是軟件開發生命周期的自然結果,我們應該接受。

隨著應用程序的發展,你的數據模型也會發生變化。你需要更改數據結構的方式,以適應新功能,同時確保用戶不會在不同版本之間丟失任何數據。如果你使用 Core Data 在應用程序中持久化信息,那么 Core Data 遷移就會發揮作用。

什么是 Core Data 遷移?

Core Data 遷移是將數據模型從一個版本更新到另一個版本的過程,因為數據的形狀發生了變化(例如,添加或刪除新屬性)。

在大多數情況下,Core Data 將自動處理遷移過程。但是,有些情況下,你需要通過提供一個映射模型來自定義遷移過程,告訴 Core Data 究竟如何從源模型遷移到目標模型中的每個屬性和實體。

甚至有些情況下,映射模型是不夠的,你需要編寫自定義遷移策略來處理特定情況。這是本文要重點討論的情況。

示例

讓我們考慮一個應用程序,在 Core Data 棧中存儲表示音樂曲目的對象。模型非常簡單,只包含一個實體:Track,Track.swift 代碼如下:

Copy code
Track.swift
import Foundation
import CoreData@objc(Track)
public class Track: NSManagedObject, Identifiable {@nonobjc public class func fetchRequest() -> NSFetchRequest<Track> {return NSFetchRequest<Track>(entityName: "Track")}@NSManaged public var imageURL: String?@NSManaged public var json: String?@NSManaged public var lastPlayedAt: Date?@NSManaged public var title: String?@NSManaged public var artistName: String?
}

上面的 Track 實體有五個屬性:

  • imageURL:表示曲目封面圖像的 URL 的字符串。
  • json:表示來自服務器的原始 JSON 數據響應的字符串。
  • lastPlayedAt:表示上次播放曲目的日期。
  • title:表示曲目的標題的字符串。
  • artistName:表示藝術家的名稱的字符串。

Core Data 棧不會與 iCloud 同步,并具有以下設置,CoreDataStack.swift 文件代碼如下:

Copy code
CoreDataStack.swift
import CoreDatastruct PersistenceController {static let shared = PersistenceController()let container: NSPersistentContainerinit(inMemory: Bool = false) {container = NSPersistentContainer(name: "CustomMigration")if inMemory {container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")}container.viewContext.automaticallyMergesChangesFromParent = trueif let description = container.persistentStoreDescriptions.first {description.shouldMigrateStoreAutomatically = truedescription.shouldInferMappingModelAutomatically = false}container.loadPersistentStores(completionHandler: { (storeDescription, error) inif let error = error as NSError? {fatalError("Unresolved error \(error), \(error.userInfo)")}})}
}

如果你仔細觀察上面的示例,你會注意到我們告訴 Core Data 自動遷移存儲,因為我們不想做漸進式遷移,這種遷移速度慢得多且更復雜,并且我們還告訴 Core Data 不要自動推斷映射模型,這意味著我們將不得不為每個遷移提供一個映射模型文件,并且可以允許我們自定義這個過程。

持久化了一首歌曲后,使用 Core Data Lab 檢查數據庫,我們可以看到屬性被相應保存:

更新模型

當前版本的模型存在一些可擴展性問題:

  1. 模型僅允許每個曲目有一個藝術家,而實際上,一個曲目可以有多個藝術家。
  2. 模型存儲一個表示曲目數據的原始 JSON 字符串,這不太高效,當應用程序需要解析 JSON 字符串以顯示曲目數據以獲取藝術家列表時,可能會導致性能問題。

為了解決這些問題,讓我們刪除 artistNamejson 屬性,采用一個新的 Artist 實體,該實體將與 Track 實體建立一對多的關系。

Artist 實體將具有一個表示藝術家名稱的 name 屬性,以及 idimageURL 屬性,我們將從原始 JSON 字符串中獲取它們。

創建一個新的模型版本

首先,讓我們通過選擇 .xcdatamodeld 文件,然后從菜單欄中選擇 Editor > Add Model Version... 來創建一個新的模型版本。

給它起一個名稱,并以第一個模型版本為基礎:

現在,讓我們創建 Artist 實體并添加所有字段:

也讓我們為新的 Artist 實體創建 NSManagedObject 子類,Artist.swift 代碼如下:

Copy code
import Foundation
import CoreData@objc(Artist)
public class Artist: NSManagedObject, Identifiable {@nonobjc public class func fetchRequest() -> NSFetchRequest<Artist> {return NSFetchRequest<Artist>(entityName: "Artist")}@NSManaged public var name: String?@NSManaged public var id: String?@NSManaged public var imageURL: String?@NSManaged public var tracks: NSSet?@objc(addTracksObject:)@NSManaged public func addToTracks(_ value: Track)@objc(removeTracksObject:)@NSManaged public func removeFromTracks(_ value: Track)@objc(addTracks:)@NSManaged public func addToTracks(_ values: NSSet)@objc(removeTracks:)@NSManaged public func removeFromTracks(_ values: NSSet)
}

正如你在上面的示例中看到的那樣,我們將向 Track 實體添加一個對多的 artists 關系,還將向 Artist 實體添加一個對多的 tracks 關系。

現在,讓我們為 Track 實體添加缺失的關系,并刪除 artistNamejson 屬性:

并更新 NSManagedObject 子類以反映更改,Track.swift 文件代碼如下:

import Foundation
import CoreData@objc(Track)
public class Track: NSManagedObject, Identifiable {@nonobjc public class func fetchRequest() -> NSFetchRequest<Track> {return NSFetchRequest<Track>(entityName: "Track")}@NSManaged public var imageURL: String?@NSManaged public var lastPlayedAt: Date?@NSManaged public var title: String?@NSManaged public var artists: NSSet?@objc(addArtistsObject:)@NSManaged public func addToArtists(_ value: Artist)@objc(removeArtistsObject:)@NSManaged public func removeFromArtists(_ value: Artist)@objc(addArtists:)@NSManaged public func addToArtists(_ values: NSSet)@objc(removeArtists:)@NSManaged public func removeFromArtists(_ values: NSSet)
}

最后但并非最不重要的,讓我們將新的模型設置為 .xcdatamodeld 文件的當前模型:

創建映射模型

由于我們告訴 Core Data 不要自動推斷映射模型,所以我們將不得不創建一個映射模型文件來在兩個版本之間建立橋梁。

從菜單欄中選擇 File > New > File...,然后選擇 Mapping Model

然后,選擇源模型:

最后,選擇目標模型:

編寫自定義遷移策略

默認情況下,Core Data 將盡力映射屬性,并且大部分工作都將由它自動完成(包括已刪除的屬性)。

然而,由于我們創建了一個新的實體,并且我們希望保留現有數據,因此我們需要告訴 Core Data 如何遷移。

我們將創建一個新的類,該類繼承自 NSEntityMigrationPolicy,并在舊的 Track 實體上創建并鏈接一個新的關系到 Artist 實體,V2MigrationPolicy.swift 文件代碼如下:

Copy code
import CoreDatastruct Song: Decodable {let artists: [Artist]struct Artist: Decodable {let id: Stringlet name: Stringlet imageURL: String}
}class V2MigrationPolicy: NSEntityMigrationPolicy {private let decoder = JSONDecoder()override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {// 1let sourceKeys = sInstance.entity.attributesByName.keyslet sourceValues = sInstance.dictionaryWithValues(forKeys: sourceKeys.map { $0 as String })// 2let destinationInstance = NSEntityDescription.insertNewObject(forEntityName: mapping.destinationEntityName!, into: manager.destinationContext)let destinationKeys = destinationInstance.entity.attributesByName.keys.map { $0 as String }// 3for key in destinationKeys {if let value = sourceValues[key] {destinationInstance.setValue(value, forKey: key)}}if let jsonString = sInstance.value(forKey: "json") as? String {// 3let jsonData = Data(jsonString.utf8)let object = try? decoder.decode(Song.self, from: jsonData)// 4let artists: [NSManagedObject] = object?.artists.map { jsonArtist in// 5let request = Artist.fetchRequest()request.fetchLimit = 1request.predicate = NSPredicate(format: "name == %@", jsonArtist.name)// Do not add duplicates to the list...if let matchedArtists = try? manager.destinationContext.fetch(request), let matchedArtist = matchedArtists.first {return matchedArtist}// 6let artist = NSEntityDescription.insertNewObject(forEntityName: "Artist", into: manager.destinationContext)artist.setValue(jsonArtist.name, forKey: "name")artist.setValue(jsonArtist.imageURL, forKey: "imageURL")artist.setValue(jsonArtist.id, forKey: "id")return artist} ?? []// 7destinationInstance.setValue(Set<NSManagedObject>(artists), forKey: "artists")}// 8manager.associate(sourceInstance: sInstance, withDestinationInstance: destinationInstance, for: mapping)}
}

讓我們逐步解釋上面的代碼:

  1. 獲取源實體的屬性名稱和值。
  2. 創建與源實體相同類型的全新目標實體。
  3. 將源實體的屬性值復制到目標實體。
  4. 如果源實體具有 json 屬性,則將其解析為 Song 對象。
  5. 為避免重復項,請檢查藝術家是否已經存在于目標上下文中。
  6. 如果藝術家不存在,則創建一個新的 Artist 實體,將其插入到上下文中,并設置其屬性。
  7. 設置目標實體上的新藝術家關系。
  8. 將源和目標實例關聯起來。

最后,讓我們將此自定義策略添加到映射模型中:

現在,如果我們再次運行應用程序并使用 Core Data Lab 檢查數據庫,我們可以看到一個新的實體已經填充了正確的數據。

總結

文章介紹了在應用程序發展過程中,數據模型可能需要進行更改的情況下,如何使用 Core Data 遷移來保持數據的一致性和完整性。首先,它解釋了什么是 Core Data 遷移,以及為什么需要進行遷移。接著,通過一個示例應用程序,詳細介紹了如何更新數據模型,添加新實體和關系,以解決現有模型的可擴展性問題。然后,文章介紹了如何創建映射模型來定義不同模型版本之間的映射關系,并演示了如何編寫自定義遷移策略來處理特定情況,例如將舊模型數據遷移到新模型的新關系中。最后,通過將自定義遷移策略添加到映射模型中,完成了整個遷移過程。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/40857.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/40857.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/40857.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

底層軟件 | 十分詳細,為了學習設備樹,我寫了5w字筆記!

0、設備樹是什么&#xff1f;1、DTS 1.1 dts簡介1.2 dts例子 2、DTC&#xff08;Device Tree Compiler&#xff09;3、DTB&#xff08;Device Tree Blob&#xff09;4、綁定&#xff08;Binding&#xff09;5、Bootloader compatible屬性 7、 #address-cells和#size-cells屬性8…

動態規劃入門,從簡單遞歸到記憶化搜索到動態規劃

動態規劃入門&#xff0c;從簡單遞歸到記憶化搜索到動態規劃 打家劫舍 class Solution {private int nums[];public int rob(int[] nums) {this.nums nums;return dfs(nums.length - 1);}public int dfs(int i){if (i < 0){return 0;}int res Math.max(dfs(i - 1), dfs(i…

127還是localhost....?

前幾天剛發現了一跨域問題&#xff0c;本來吧跨域問題也挺好解決的。 網上搜點教程&#xff0c;該怎么配置就怎么配置就完事了。 但是今天這個跨域問題有點棘手&#xff0c;問題就出在127.0.0.1還是localhost上面 先放一下一開始在127.0.0.1解決跨域的代碼 前端 HTML <…

Vim腳本編寫:自動化任務與自定義命令

Vim腳本&#xff08;Vim Script&#xff09;是一種強大的工具&#xff0c;用于擴展和自動化Vim編輯器的功能。通過編寫Vim腳本&#xff0c;你可以創建自定義命令、自動化常見任務、增強編輯器功能&#xff0c;以及提高你的工作效率。本文將介紹Vim腳本編寫的基礎知識和一些實用…

預制菜工廠MES系統:具體功能與應用場景

在現代化食品工業中&#xff0c;預制菜&#xff08;Ready-to-Eat, RTE&#xff09;因其方便快捷、衛生安全及營養均衡的特點&#xff0c;迅速在餐飲行業中占據重要地位。為了進一步提升預制菜工廠的生產效率、保障產品質量并降低生產成本&#xff0c;制造執行系統&#xff08;M…

代碼隨想錄訓練營第二十八天 122買賣股票的最佳時間II 55跳躍游戲 45跳躍游戲II 1005K次取反后最大化的數組和

第一題&#xff1a; 原題鏈接&#xff1a;122. 買賣股票的最佳時機 II - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 這題十分簡單&#xff0c;就是把相鄰天數的金額相減&#xff0c;如果發現大于0就加到res中&#xff0c;返回res即可 代碼如下&#xff1a; …

error: ‘CV_FONT_HERSHEY_SIMPLEX’ was not declared in this scope 的參考解決方法

文章目錄 寫在前面一、問題描述二、解決方法參考鏈接 寫在前面 自己的測試環境&#xff1a; Ubuntu20.04&#xff0c;OpenCV 4.2.0 一、問題描述 編譯 OpenCV 的程序時&#xff0c;出現如下報錯 error: ‘CV_FONT_HERSHEY_SIMPLEX’ was not declared in this scope二、解決…

MySQL中的可插拔身份驗證(Pluggable Authentication)(二)

Pluggable Authentication&#xff08;PAM&#xff0c;即可插拔式認證模塊&#xff09;是一種高效且靈活的用戶級別的認證方式&#xff0c;廣泛應用于現代操作系統&#xff0c;特別是Linux服務器中。它允許數據庫管理員&#xff08;DBAs&#xff09;為MySQL用戶帳戶選擇和更改不…

ffmpeg將多個yuv文件編碼為MP4視頻文件

一、編碼方案 在視頻錄制時&#xff0c;每一幀保存為一個yuv文件&#xff0c;便于糾錯和修改。在編碼為MP4文件時&#xff0c;我的方案是將所有yuv文件先轉碼為單個MP4文件&#xff0c;然后使用ffmpeg的concat功能拼接為完整的視頻。 二、shell腳本 #!/bin/bash# 檢查參數數量…

MYSQL8.0環境部署

創建用戶 groupadd mysql useradd -g mysql mysql 刪除原來的包 # rpm -qa|grep mysql # rpm -qa|grep mari mariadb-libs-5.5.68-1.el7.x86_64 # rpm -e --nodeps mariadb-libs-5.5.68-1.el7.x86_64 解壓 cd /usr/local & mkdir mysql cd mysql # cp mysql-8…

Ubuntu 22.04 安裝中文字體

筆者在用OpenCV4.9處理圖片加水印時&#xff0c;中文亂碼。原來是Ubuntu 22.04發行版缺少中文字體支持&#xff0c;因此&#xff0c;筆者就找資料安裝了需要的中文字體&#xff0c;特此記錄&#xff0c;以備后查。 1、打開終端&#xff1a; 2、更新軟件包列表&#xff1a; su…

【LC刷題】DAY22:491 46 47 332 51 37

【LC刷題】DAY22&#xff1a;491 46 47 332 51 37 文章目錄 【LC刷題】DAY22&#xff1a;491 46 47 332 51 37491. 非遞減子序列 [link](https://leetcode.cn/problems/non-decreasing-subsequences/description/)46. 全排列 [link](https://leetcode.cn/problems/permutations…

水利行業的智慧化轉型實踐:結合具體案例,探討智慧水利在提升水資源利用效率、改善水生態環境方面的實際效果

目錄 一、引言 二、智慧水利的定義與意義 三、智慧水利在提升水資源利用效率方面的實踐 1. 智慧灌溉系統 2. 智慧供水系統 3. 智慧水務管理平臺 四、智慧水利在改善水生態環境方面的實踐 1. 智慧水質監測系統 2. 智慧水生態修復系統 3. 智慧防洪減災系統 五、具體案例…

如何在 Odoo 16 中添加計算字段的搜索過濾器

首先&#xff0c;了解 Odoo 使用計算字段的原因很重要。當我們需要從其他字段獲取計算值或計算值時&#xff0c;就會使用計算字段。換句話說&#xff0c;不是從數據庫中檢索值&#xff0c;而是可以使用函數計算字段的值。計算字段的一個例子是產品總金額&#xff0c;即通過將產…

EtherCAT通訊介紹

一、EtherCAT簡介 EtherCAT&#xff08;Ethernet for Control Automation Technology&#xff09;是一種實時以太網技術&#xff0c;是由德國公司Beckhoff Automation在2003年首次推出的。它是一種開放的工業以太網標準&#xff0c;被設計用于滿足工業自動化應用中的高性能和低…

匯聚榮拼多多評價好不好?

匯聚榮拼多多評價好不好?在探討電商平臺的口碑時&#xff0c;用戶評價是衡量其服務質量和商品質量的重要指標。拼多多作為國內領先的電商平臺之一&#xff0c;其用戶評價自然成為消費者選擇購物平臺時的參考依據。針對“匯聚榮拼多多評價好不好?”這一問題&#xff0c;可以從…

Vue3 Hooks 用法 scrollTop, mousemoveHandler,useCountDown

三個實例來自 learn_vue: 【教學工程】學習vue2/vue3 (gitee.com) 目錄 1. 何為Hooks 2. 使用場景 3. 常見的 Hooks 函數 4. 實例 4.1簡易hook 例子 4.2 自定義scrolltop例子 4.3 mousemoveHandler例子 4.4 useCountDown例子 1. 何為Hooks Hooks 是一種函數&#xff0c;用于…

vue css 鏈式布局模式

<div class"pp-wrap"> <div class"pp-left"><!--跳活動反思--><div class"even-box" v-for"(item,index) in trackingPtoPLeftList" :key"index" click"jumpReview(item)"><div …

echarts柱狀選中shadow陰影背景寬度設置

使用line&#xff0c;寬度增大到所需要的寬度&#xff0c;設置下顏色透明度就行 tooltip: {trigger: axis,//把陰影的層級往下降z:-15,axisPointer: {type: line,lineStyle: {color: rgba(150,150,150,0.3),width: 44,type: solid,},}, }, series: [{type: bar,barWidth:20,//…

python自動化辦公之BeautifulSoup爬取并解析html文本

用到的庫&#xff1a;BeautifulSoup 實現效果&#xff1a;爬取網站內容&#xff0c;拿到html文本并解析html文本 代碼&#xff1a; 先爬取 # 先導入requests包 import requests urlhttps://www.baidu.com responserequests.get(url) # 做1個斷言&#xff0c;如果執行成功&a…