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.mzph.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…

预制菜工厂MES系统:具体功能与应用场景

在现代化食品工业中&#xff0c;预制菜&#xff08;Ready-to-Eat, RTE&#xff09;因其方便快捷、卫生安全及营养均衡的特点&#xff0c;迅速在餐饮行业中占据重要地位。为了进一步提升预制菜工厂的生产效率、保障产品质量并降低生产成本&#xff0c;制造执行系统&#xff08;M…

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…

水利行业的智慧化转型实践:结合具体案例,探讨智慧水利在提升水资源利用效率、改善水生态环境方面的实际效果

目录 一、引言 二、智慧水利的定义与意义 三、智慧水利在提升水资源利用效率方面的实践 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;可以从…

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…

【C语言】—— 文件操作(上)

【C语言】—— 文件操作&#xff08;上&#xff09; 一、 为什么使用文件二、 什么是文件2.1、 程序文件2.2、 数据文件2.3、 文件名2.4、二进制文件与文本文件 三、 文件的打开和关闭3.1、流和标准流&#xff08;1&#xff09;流&#xff08;2&#xff09;标准流 3.2、文件指针…

64.函数参数和指针变量

目录 一.函数参数 二.函数参数和指针变量 三.视频教程 一.函数参数 函数定义格式&#xff1a; 类型名 函数名(函数参数1,函数参数2...) {代码段 } 如&#xff1a; int sum(int x&#xff0c;int y) {return xy; } 函数参数的类型可以是普通类型&#xff0c;也可以是指针类…

Python可实现各种算法库之algorithms使用详解

概要 在软件开发和计算机科学领域,算法是解决问题的核心工具。Python 作为一种广泛使用的编程语言,提供了多种内置和第三方库来实现各种算法。algorithms 库是一个集合了多种常用算法和数据结构的 Python 库,旨在帮助开发者快速实现和应用这些算法。本文将详细介绍 algorit…

Ollama基于Casaos一键部署,并接入Dify知识库,无需再为API付费

什么是Ollama Ollama是一个开源的大型语言模型服务工具&#xff0c;它帮助用户快速的运行大模型。浪浪云将它做为一键部署通过简单的安装&#xff0c;用户可以执行一条命令就可以运行开源大型语言模型&#xff0c;如 llama3 ,通以千问。极大地简化了部署和管理LLM的过程&#x…

modelscope可控细节的长文档摘要

modelscope可控细节的长文档摘要尝试 本文的想法来自今年OpenAI cookbook的一篇实践&#xff1a;summarizing_long_documents&#xff0c;目标是演示如何以可控的细节程度总结大型文档。 如果我们想让大语言模型总结一份长文档&#xff08;例如 10k 或更多tokens&#xff09;&…

6个步骤实现Postman接口压力测试(建议收藏)

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 这里讲是postman做接口并发测试&#xff0c;基础用法不做赘述 1、第一步接口可以通的情况下点击…

Proteus-51单片机-DS18B20多点测温

DS18B20多点测温 一、Proteus仿真演示 每个DS18B20都有一个唯一的64位序列号,这使得在同一总线上可以挂载多个传感器,无需额外的地址分配。主机(通常为单片机)通过特定的时序控制,可以依次读取各个DS18B20的温度数据,实现分布式测温。 二、代码特点 三、开发环境介绍 本…