随着应用程序和用户群的增长,你需要添加新功能,删除其他功能,并改变应用程序的工作方式。这是软件开发生命周期的自然结果,我们应该接受。
随着应用程序的发展,你的数据模型也会发生变化。你需要更改数据结构的方式,以适应新功能,同时确保用户不会在不同版本之间丢失任何数据。如果你使用 Core Data 在应用程序中持久化信息,那么 Core Data 迁移就会发挥作用。
Core Data 迁移是将数据模型从一个版本更新到另一个版本的过程,因为数据的形状发生了变化(例如,添加或删除新属性)。
在大多数情况下,Core Data 将自动处理迁移过程。但是,有些情况下,你需要通过提供一个映射模型来自定义迁移过程,告诉 Core Data 究竟如何从源模型迁移到目标模型中的每个属性和实体。
甚至有些情况下,映射模型是不够的,你需要编写自定义迁移策略来处理特定情况。这是本文要重点讨论的情况。
让我们考虑一个应用程序,在 Core Data 栈中存储表示音乐曲目的对象。模型非常简单,只包含一个实体:Track
,Track.swift 代码如下:
Copy codeTrack.swiftimport Foundationimport 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 实体有五个属性:
Core Data 栈不会与 iCloud 同步,并具有以下设置,CoreDataStack.swift 文件代码如下:
Copy codeCoreDataStack.swiftimport CoreDatastruct PersistenceController { static let shared = PersistenceController() let container: NSPersistentContainer init(inMemory: Bool = false) { container = NSPersistentContainer(name: "CustomMigration") if inMemory { container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") } container.viewContext.automaticallyMergesChangesFromParent = true if let description = container.persistentStoreDescriptions.first { description.shouldMigrateStoreAutomatically = true description.shouldInferMappingModelAutomatically = false } container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { fatalError("Unresolved error /(error), /(error.userInfo)") } }) }}
如果你仔细观察上面的示例,你会注意到我们告诉 Core Data 自动迁移存储,因为我们不想做渐进式迁移,这种迁移速度慢得多且更复杂,并且我们还告诉 Core Data 不要自动推断映射模型,这意味着我们将不得不为每个迁移提供一个映射模型文件,并且可以允许我们自定义这个过程。
持久化了一首歌曲后,使用 Core Data Lab 检查数据库,我们可以看到属性被相应保存:
当前版本的模型存在一些可扩展性问题:
为了解决这些问题,让我们删除 artistName 和 json 属性,采用一个新的 Artist 实体,该实体将与 Track 实体建立一对多的关系。
Artist 实体将具有一个表示艺术家名称的 name 属性,以及 id 和 imageURL 属性,我们将从原始 JSON 字符串中获取它们。
首先,让我们通过选择 .xcdatamodeld 文件,然后从菜单栏中选择 Editor > Add Model Version... 来创建一个新的模型版本。
给它起一个名称,并以第一个模型版本为基础:
现在,让我们创建 Artist 实体并添加所有字段:
也让我们为新的 Artist 实体创建 NSManagedObject 子类,Artist.swift 代码如下:
Copy codeimport Foundationimport 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 实体添加缺失的关系,并删除 artistName 和 json 属性:
并更新 NSManagedObject 子类以反映更改,Track.swift 文件代码如下:
import Foundationimport 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 codeimport CoreDatastruct Song: Decodable { let artists: [Artist] struct Artist: Decodable { let id: String let name: String let imageURL: String }}class V2MigrationPolicy: NSEntityMigrationPolicy { private let decoder = JSONDecoder() override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws { // 1 let sourceKeys = sInstance.entity.attributesByName.keys let sourceValues = sInstance.dictionaryWithValues(forKeys: sourceKeys.map { $0 as String }) // 2 let destinationInstance = NSEntityDescription.insertNewObject(forEntityName: mapping.destinationEntityName!, into: manager.destinationContext) let destinationKeys = destinationInstance.entity.attributesByName.keys.map { $0 as String } // 3 for key in destinationKeys { if let value = sourceValues[key] { destinationInstance.setValue(value, forKey: key) } } if let jsonString = sInstance.value(forKey: "json") as? String { // 3 let jsonData = Data(jsonString.utf8) let object = try? decoder.decode(Song.self, from: jsonData) // 4 let artists: [NSManagedObject] = object?.artists.map { jsonArtist in // 5 let request = Artist.fetchRequest() request.fetchLimit = 1 request.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 } // 6 let 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 } ?? [] // 7 destinationInstance.setValue(Set<NSManagedObject>(artists), forKey: "artists") } // 8 manager.associate(sourceInstance: sInstance, withDestinationInstance: destinationInstance, for: mapping) }}
让我们逐步解释上面的代码:
最后,让我们将此自定义策略添加到映射模型中:
现在,如果我们再次运行应用程序并使用 Core Data Lab 检查数据库,我们可以看到一个新的实体已经填充了正确的数据。
文章介绍了在应用程序发展过程中,数据模型可能需要进行更改的情况下,如何使用 Core Data 迁移来保持数据的一致性和完整性。首先,它解释了什么是 Core Data 迁移,以及为什么需要进行迁移。接着,通过一个示例应用程序,详细介绍了如何更新数据模型,添加新实体和关系,以解决现有模型的可扩展性问题。然后,文章介绍了如何创建映射模型来定义不同模型版本之间的映射关系,并演示了如何编写自定义迁移策略来处理特定情况,例如将旧模型数据迁移到新模型的新关系中。最后,通过将自定义迁移策略添加到映射模型中,完成了整个迁移过程。
本文链接://www.dmpip.com//www.dmpip.com/showinfo-26-88935-0.htmlSwift 定制 Core Data 迁移
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
上一篇: 谁说PHP不能异步和并行运行?