变幻莫测:CoreData 中 Transformable 类型面面俱到(七)
概述
各位似秃似不秃小码农们都知道,在苹果众多开发平台中 CoreData 无疑是那个最简洁、拥有“官方认证”且最具兼容性的数据库框架。使用它可以让我们非常方便的搭建出 App 所需要的持久存储体系。
不过,大家是否知道在 CoreData 中还存在一个 Transformable 类型,它到底是个啥?应用场景有哪些?在最新的 SwiftData 中有没有对应物?对于开发者又有哪些“见雀张罗”的撸码陷阱和最佳实践呢?
在本篇博文中,您将学到如下内容:
- 概述
- 6.3 Skill 的赋值与读取
- 6.4 最后一块拼图:实现 SkillTransformer 转换器
- 总结
本系列文章一共包括将近 3w 枚机智而幽默的文字、详实的大段代码示例以及海量图片,定能让小伙伴们对 Transformable 类型的“驾驭”更加胸有成竹、胜券在握!
那还等什么呢?让我们马上开始 Transformable 大冒险吧!
Let’s go!!!😉
6.3 Skill 的赋值与读取
在上一篇博文中我们已经介绍了 Skill 类及其子类的实现。接下来,我们可以在英雄和恶棍“出生”时为它们增加 Skill 技能啦:
let newHero = Hero(context: context)
newHero.skill = HeroSkill.glarelet newVillain = Villain(context: context)
newVillain.skill = VillainSkill.swallow
当然,别忘了最后需要将这些威力强大的技能“调皮地”显示在视图中哦:
if let skill = villain.skill as? VillainSkill {Section("恶棍技能") {LabeledContent("技能名称") {Text(skill.name)}LabeledContent("邪恶威慑力") {Text("\(skill.evalPower)")}}
}if let skill = hero.skill as? HeroSkill {Section("英雄技能") {LabeledContent("技能名称") {Text(skill.name)}LabeledContent("正义威慑力") {Text("\(skill.justicePower)")}}.foregroundStyle(.red)
}
6.4 最后一块拼图:实现 SkillTransformer 转换器
现在,只剩 Skill 的转换器 SkillTransformer 还未实现了,让我们马上开始搞定它吧!
首先,创建一个 SkillTransformer 类,并让它遵守 NSSecureUnarchiveFromDataTransformer 协议:
class SkillTransformer: NSSecureUnarchiveFromDataTransformer {override class func allowsReverseTransformation() -> Bool {return true}override class func transformedValueClass() -> AnyClass {return Skill.self}override class var allowedTopLevelClasses: [AnyClass] {return [Skill.self, VillainSkill.self, HeroSkill.self, NSString.self]}override func transformedValue(_ value: Any?) -> Any? {guard let data = value as? Data else {fatalError("错误的数据类型")}return super.transformedValue(data)}override func reverseTransformedValue(_ value: Any?) -> Any? {guard let color = value as? Skill else {fatalError("错误的数据类型")}return super.reverseTransformedValue(color)}
}extension NSValueTransformerName {static let skill = NSValueTransformerName(rawValue: "SkillTransformer")
}
不知小伙伴们是否记得,在之前的代码中我们留了一个坑:就是转换器如何把实际需要转换的类型“报备”给系统,这就是上面我们需要重写 allowedTopLevelClasses 计算属性,以返回所需“报备”类的原因了。
可以看到,我们不仅返回了 Skill 基类自身,同时还返回了 Skill 所有子类以及其它内置的类型,这样做可以确保系统能会我们的意图“心领神会”。
最后,我们只需在 App 启动的极早期注册 SkillTransformer 类就可以啦:
struct PersistenceController {static let shared = PersistenceController()let container: NSPersistentContainerinit(inMemory: Bool = false) {// 注册 SkillTransformer 转换器ValueTransformer.setValueTransformer(SkillTransformer(), forName: .skill)container = NSPersistentContainer(name: "CDTransformableTest")if inMemory {container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")}container.loadPersistentStores(completionHandler: { (storeDescription, error) inif let error = error as NSError? {fatalError("Unresolved error \(error), \(error.userInfo)")}})container.viewContext.automaticallyMergesChangesFromParent = true}
}
注意,如果大家的 App 有一个全局 Model 类型,切勿将 Transformable 转换器放到该类型的构造器中:
class Model: ObservableObject {static let shared = Model()private init() {// 不要这样做!❌ValueTransformer.setValueTransformer(SkillTransformer(), forName: .skill)}
}@main
struct CDTransformableTestApp: App {let model = Model.sharedlet persistenceController = PersistenceController.sharedvar body: some Scene {WindowGroup {MainView().environmentObject(model).environment(\.managedObjectContext, persistenceController.container.viewContext)}}
}
因为就本例来看。这样做会导致 SkillTransformer 在 CoreData 持久容器实例初始化之后才被注册,从而无法起到转换器应有的作用了。
至此,我们已经如约完成了 4 种至 Transformable 的转换场景,在最后一篇博文中,我们将讨论 Transformable 与纯 Data 类型的区别,以及 Transformable 在 SwiftData 里的实现,敬请期待吧!
总结
在本篇博文中,我们收尾了 Skill 及其子类到 Transformable 类型的转换,并实现了 Skill 的转换器 SkillTransformer。
感谢观赏,最后一篇再会吧!😎