Swift Core Data 分阶段迁移

在这里插入图片描述

在这里插入图片描述

文章目录

    • 前言
    • 什么是分阶段迁移?
    • 提供一些背景信息
    • 创建迁移管理器
    • 设置使用 Core Data 栈。
    • 总结

前言

在这之前,我发布了一篇文章,在其中解释了如何使用映射模型和自定义迁移策略执行复杂的 Core Data 迁移。虽然这种方法性能良好且运行良好,但很难维护,不适用于应用程序扩展,并且存在高度的错误风险。

例如,对于每个需要自定义迁移的新模型,你需要定义一个映射模型,以定义如何将每个模型的现有版本迁移到新版本。与你可能认为的相反(以及我所认为的),Core Data 在跨多个版本进行迁移时并不会按顺序迭代映射模型,相反,它需要从当前版本到新版本的精确模型。

除此之外,你需要使用 Xcode 的 UI 和映射模型来定义所有这些内容,这使得 PR 难以审查,错误难以发现。出于这些原因,我最近重新设计了我们的迁移流程,改用分阶段迁移,对开发者体验产生了巨大的影响!

什么是分阶段迁移?

正如在 WWDC23 中宣布的那样,与在 Swift 数据模型之间执行迁移的方式非常相似,你现在可以使用 NSStagedMigrationManager 实例以编程方式定义 Core Data 迁移。

该方法通过定义一系列迁移步骤(称为阶段),描述了如何在模型的不同版本之间进行迁移。

例如,假设你的应用程序当前正在使用数据模型的第 1 版,你想要迁移到第 3 版。迁移管理器将顺序应用所有必要的阶段,以从第 1 版迁移到第 2 版,然后从第 2 版迁移到第 3 版。

提供一些背景信息

为了演示 Core Data 分阶段迁移的工作原理,我将使用我之前在有关使用映射模型进行自定义 Core Data 迁移的文章中使用的相同示例。

与之前的文章一样,我们想要将 Track 模型中的 json 属性转换为一个单独的实体,该实体将为每个曲目保存所有相关的艺术家信息。将此属性转换也将使模型更灵活、更易于维护,因为我们将能够删除 json 属性本身和 artistName,而使用新的关系。

让我们比较一下我们的 Track 模型之前和之后的情况,CoreData.swift 文件代码如下:

Copy code
CoreData.swift
// Before
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?
}// After@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)
}@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)
}

从上面的代码中可以看出,迁移并不是微不足道的,而且,对我们来说,Core Data 不能自动推断它。让我们看看如何使用分阶段迁移以代码形式定义迁移步骤。

创建迁移管理器

要定义我们的阶段,我们需要将我们的模型拆分为三个不同的模型版本和迁移:

  1. 保持原始模型版本不变。
  2. 第二个模型版本包含所有属性,并添加 Artist 实体和关系。这将是一个自定义阶段。
  3. 第三个模型版本删除了 jsonartistName 属性。这将是一个轻量级的阶段。

我们需要将迁移分解为三个阶段的原因是,就目前而言,我们不能在同一个阶段中使用并删除属性。

让我们从创建一个负责创建 NSStagedMigrationManager 实例并定义所有阶段的工厂类开始。StagedMigrationFactory.swift 文件代码如下:

import Foundation
import CoreData
import OSLog// 1
extension Logger {private static var subsystem = "dev.polpiella.CustomMigration"static let storage = Logger(subsystem: subsystem, category: "Storage")
}// 2
extension NSManagedObjectModelReference {convenience init(in database: URL, modelName: String) {let modelURL = database.appending(component: "\(modelName).mom")guard let model = NSManagedObjectModel(contentsOf: modelURL) else { fatalError() }self.init(model: model, versionChecksum: model.versionChecksum)}
}// 3
final class StagedMigrationFactory {private let databaseURL: URLprivate let jsonDecoder: JSONDecoderprivate let logger: Loggerinit?(bundle: Bundle = .main,jsonDecoder: JSONDecoder = JSONDecoder(),logger: Logger = .storage) {// 4guard let databaseURL = bundle.url(forResource: "CustomMigration", withExtension: "momd") else { return nil }self.databaseURL = databaseURLself.jsonDecoder = jsonDecoderself.logger = logger}// 5func create() -> NSStagedMigrationManager {let allStages = [v1toV2(),v2toV3()]return NSStagedMigrationManager(allStages)}// 6private func v1toV2() -> NSCustomMigrationStage {struct Song: Decodable {let artists: [Artist]struct Artist: Decodable {let id: Stringlet name: Stringlet imageURL: String}}// 7let customMigrationStage = NSCustomMigrationStage(migratingFrom: NSManagedObjectModelReference(in: databaseURL, modelName: "CustomMigration"),to: NSManagedObjectModelReference(in: databaseURL, modelName: "CustomMigration 2"))// 8customMigrationStage.didMigrateHandler = { migrationManager, currentStage inguard let container = migrationManager.container else {return}// 9let context = container.newBackgroundContext()context.performAndWait {let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Track")fetchRequest.predicate = NSPredicate(format: "json != nil")do {let allTracks = try context.fetch(fetchRequest)let addedArtists = [String: NSManagedObject]()for track in allTracks {if let jsonString = track.value(forKey: "json") as? String {let jsonData = Data(jsonString.utf8)let object = try? self.jsonDecoder.decode(Song.self, from: jsonData)let artists: [NSManagedObject] = object?.artists.map { jsonArtist inif let matchedArtist = addedArtists[jsonArtist.id] {return matchedArtist}let artist = NSEntityDescription.insertNewObject(forEntityName: "Artist",into: context)artist.setValue(jsonArtist.name, forKey: "name")artist.setValue(jsonArtist.imageURL, forKey: "imageURL")artist.setValue(jsonArtist.id, forKey: "id")return artist} ?? []track.setValue(Set<NSManagedObject>(artists), forKey: "artists")}}try context.save()} catch {logger.error("\(error.localizedDescription)")}}}return customMigrationStage}// 10private func v2toV3() -> NSCustomMigrationStage {NSCustomMigrationStage(migratingFrom: NSManagedObjectModelReference(in: databaseURL, modelName: "CustomMigration 2"),to: NSManagedObjectModelReference(in: databaseURL, modelName: "CustomMigration 3"))}
}

回到上面的代码,让我们逐步分解:

  1. 我们定义了一个自定义记录器,以将迁移过程中发生的任何错误报告到控制台。
  2. 我们扩展了 NSManagedObjectModelReference,创建了一个方便的初始化方法,它接受数据库 URL 和模型名称,并返回一个新的 NSManagedObjectModelReference 实例。
  3. 我们定义了一个工厂类,负责创建 NSStagedMigrationManager 实例并定义所有阶段。
  4. 我们使用 bundle 初始化工厂,并检索数据库的 URL、JSON 解码器和记录器。
  5. 我们创建了 NSStagedMigrationManager 实例,并定义了所有阶段。
  6. 我们定义了一个方法,该方法将返回从我们模型的第 1 版迁移到第 2 版的迁移阶段。
  7. 我们创建了一个 NSCustomMigrationStage 实例,并传递我们要从何处迁移和迁移到的对象模型引用。文件名需要与包中的 .mom 文件的名称匹配。
  8. 我们定义了 didMigrateHandler 闭包,在模型迁移后调用。此时,新的模型版本可在上下文中使用,你可以填充其属性。你必须知道,还有一个在先前模型版本上执行的单独处理程序,称为 willMigrateHandler,但我们在这种情况下不会使用它。
  9. 我们创建了一个新的后台上下文,并获取所有具有 json 属性的曲目。然后,我们将 JSON 字符串解码为 Song 对象,并为 JSON 中的每个艺术家创建一个新的 Artist 实体。然后,我们将 Track 实体的 artists 关系设置为新的 Artist 实体。
  10. 我们定义了一个方法,该方法将返回从我们模型的第 2 版迁移到第 3 版的迁移阶段。这个迁移非常简单,事实上,它应该是一个轻量级的迁移。然而,我找不到一个能够在所有情况下使用的 NSLightweightMigrationStage 实例的方法。如果你知道如何做,请告诉我!

设置使用 Core Data 栈。

设置使用分阶段迁移的 Core Data 栈。

现在我们有了创建 NSStagedMigrationManager 实例的方法,我们需要设置我们的 Core Data 栈以使用它。PersistenceController.swift 文件代码如下:

PersistenceController.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 {if let migrationFactory = StagedMigrationFactory() {description.setOption(migrationFactory.create(), forKey: NSPersistentStoreStagedMigrationManagerOptionKey)}}container.loadPersistentStores(completionHandler: { (storeDescription, error) inif let error = error as NSError? {fatalError("Unresolved error \(error), \(error.userInfo)")}})}
}

这部分非常简单,你只需要将 NSStagedMigrationManager 实例设置为持久化存储描述的选项。

总结

这篇文章介绍了使用分阶段迁移来改进 Core Data 迁移流程的重要性和方法。传统的迁移方法使用映射模型,但这种方法不易维护,扩展性差且容易出错。分阶段迁移通过定义一系列迁移步骤,使得在不同模型版本之间进行迁移变得更加简单和可控。文章以一个示例来说明分阶段迁移的工作原理,以及如何以代码形式定义迁移步骤。最后,文章展示了如何设置使用分阶段迁移的 Core Data 栈。通过使用分阶段迁移,可以显著提高开发者体验,简化迁移流程,并降低错误风险。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/40666.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Linux进阶】文件和目录的默认权限与隐藏权限

1.文件默认权限&#xff1a;umask OK&#xff0c;那么现在我们知道如何建立或是改变一个目录或文件的属性了&#xff0c;不过&#xff0c;你知道当你建立一个新的文件或目录时&#xff0c;它的默认权限会是什么吗&#xff1f; 呵呵&#xff0c;那就与umask这个玩意儿有关了&…

Vue85-Vuex的求和案例

一、需求 二、开发 2-1、index.js中vuex的代码 注意&#xff1a; 书写格式&#xff1a;actions中的函数名用小写&#xff01;mutations中的函数名&#xff0c;用大写。 注意&#xff1a; 2-2、组件count.vue中的代码 2-3、代码优化 三、actions中的context参数 此写法的后…

Python基于PyQt5和卷积神经网络分类模型(ResNet50分类算法)实现生活垃圾分类系统GUI界面项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 在当今社会&#xff0c;随着人们对环境保护意识的增强以及科技的快速发展&#xff0c;智能化的垃圾分类…

EasyBoss ERP移动端上线数据分析模块,随时查Shopee/TikTok本土店数据

前段时间&#xff0c;EasyBoss ERP出了个超酷炫的数字大屏功能&#xff0c;广受好评。 但是也有老板说&#xff0c;电脑端看数据不够方便啊&#xff0c;你们EasyBoss有本事上个手机就能看数据的功能啊&#xff01; 说干就干&#xff0c;直接满足你们的需求&#xff01; 于是在…

day02-统计数据

numpy统计学 1.求平均值[数组名.mean()/np.mean(数组名)] m1 np.arange(20).reshape((4,5))m1.mean() #9.5若想要求某一维的平均值&#xff0c;设置axis参数&#xff0c;多维数组元素指定&#xff1a; axis 0&#xff0c;将从上往下计算。axis 1&#xff0c;将从左往右计算…

44 mysql batch insert 的实现

前言 我们这里 来探讨一下 insert into $fields values ($values1), ($values2), ($values3); 的相关实现, 然后 大致来看一下 为什么 他能这么快 按照 我的思考, 应该里里面有 批量插入才对, 但是 调试结果 发现令我有一些意外 呵呵 果然 只有调试才是唯一的真理 相比于 …

Linux的Socket开发概述

套接字&#xff08;socket&#xff09;是 Linux 下的一种进程间通信机制&#xff08;socket IPC&#xff09;&#xff0c;在前面的内容中已经给大家提到过&#xff0c;使用 socket IPC 可以使得在不同主机上的应用程序之间进行通信&#xff08;网络通信&#xff09;&#xff0c…

MATLAB和Python发那科ABB库卡史陶比尔工业机器人模拟示教框架

&#x1f3af;要点 &#x1f3af;模拟工业机器人 | &#x1f3af;可视化机器人DH 参数&#xff0c;机器人三维视图 | &#x1f3af;绘制观察运动时关节坐标位置、速度和加速度 | &#x1f3af;绘制每个关节处的扭矩和力 | &#x1f3af;图形界面示教机器人 | &#x1f3af;工业…

Qt入门小项目 | WPS tab页面(无边框窗口综合应用)

文章目录 一、手写代码实现WPS tab页面 一、手写代码实现WPS tab页面 实现类似WPS tab效果&#xff0c;具体包含&#xff1a; 自定义标题栏&#xff1a;最大、最小、关闭在QTabWidget的tab上增加控件在QTabWidget的tab上右键菜单可拖拽移动可拉伸窗口双击标题栏在最大与正常间…

Objection 对命令的批量操作

假定现在需要对好多不同的类进行批量hook&#xff0c;逐个hook非常繁琐&#xff0c;那么可以要将这些hook的类放到一个文件里&#xff0c;并且在这些类的前面加上hook命令&#xff0c;内容如下 使用如下命令执行该文件中的命令 objection -g 测试 explore -c d:/hookData/toHoo…

昇思25天学习打卡营第13天|ResNet50图像分类

1. 学习内容复盘 图像分类是最基础的计算机视觉应用&#xff0c;属于有监督学习类别&#xff0c;如给定一张图像(猫、狗、飞机、汽车等等)&#xff0c;判断图像所属的类别。本章将介绍使用ResNet50网络对CIFAR-10数据集进行分类。 ResNet网络介绍 ResNet50网络是2015年由微软…

传承与创新,想让认字更简单?就来看《米小圈动画汉字》吧!

汉字&#xff0c;作为中华文化的精髓和根基&#xff0c;自古以来便承载着中华民族的思想与记忆。在现代社会&#xff0c;随着文化多样性的崛起和科技进步的推动&#xff0c;汉字的教育也更加的多元化&#xff0c;《米小圈动画汉字》作为一项全新的教育资源&#xff0c;不仅致力…

【python基础】—calendar模块

文章目录 前言一、calendar模块方法1.firstweekday()2.setfirstweekday(firstweekday)3.isleap(year)4.leapdays(y1, y2)5.weekday(year, month, day)6.monthrange(year, month)7.weekheader(n)8.monthcalendar(year, month)9.prmonth(theyear, themonth, w0, l0)10.prcal(year…

【硬核科普】存算一体化系统(Processing-in-Memory, PIM)深入解析

文章目录 0. 前言1. 提出背景1.1 存储墙1.2 功耗墙 2. 架构方案2.1 核心特征2.2 技术实现2.2.1 电流模式2.2.2 电压模式2.2.3 模式选择 2.3 PIM方案优势 3. 应用场景4. 典型产品4.1 鸿图H304.2 三星HBM-PIM 5. 存算一体化缺点6. 总结 0. 前言 按照国际惯例&#xff0c;首先声明…

c++类模板及应用

文章目录 为什么要有函数模板一般实现举例类模板举例 继承中类模板的使用特殊情况 友元函数模板类和静态成员类模板实践 为什么要有函数模板 项目需求: 实现多个函数用来返回两个数的最大值&#xff0c;要求能支持char类型、int类型、double 一般实现举例 类模板举例 继承中类…

如视“VR+AI”实力闪耀2024世界人工智能大会

7月4日&#xff0c;2024世界人工智能大会暨人工智能全球治理高级别会议&#xff08;以下简称为“WAIC 2024”&#xff09;在上海盛大开幕&#xff0c;本届大会由外交部、国家发展和改革委员会、教育部等部门共同主办&#xff0c;围绕“以共商促共享 以善治促善智”主题&#xf…

【SSL 1823】消灭怪物(非传统BFS)

题目大意 小b现在玩一个极其无聊的游戏&#xff0c;它控制角色从基地出发&#xff0c;一路狂奔夺走了对方的水晶&#xff0c;可是正准备回城时&#xff0c;发现地图上已经生成了 n n n 个怪。 现在假设地图是二维平面&#xff0c;所有的怪和角色都认为是在这个二维平面的点上…

【算法训练记录——Day41】

Day41——动态规划Ⅲ 1.理论基础——代码随想录2.纯01背包_[kamacoder46](https://kamacoder.com/problempage.php?pid1046)3.leetcode_416分割等和子集 背包&#xff01;&#xff01; 1.理论基础——代码随想录 主要掌握01背包和完全背包 物品数量&#xff1a; 只有一个 ——…

农作物生长环境的远程监控与智能调控

农作物生长环境的远程监控与智能调控 农作物生长环境的远程监控与智能调控技术&#xff0c;作为现代农业科技的核心组成部分&#xff0c;正逐步革新传统农业的生产模式&#xff0c;推动农业向精准化、智能化转型。这一技术体系综合应用了物联网、大数据、云计算以及人工智能等…

chrome 谷歌浏览器插件打包

1、找到id对应的字符串去搜索 C:\Users\<你的用户名>\AppData\Local\Google\Chrome\User Data\Default\Extensions2、选择根目录 直接加载下面的路径扩展可用&#xff1a;