概述
各位似秃似不秃小码农们都知道,在苹果众多开发平台中 CoreData 无疑是那个最简洁、拥有“官方认证”且最具兼容性的数据库框架。使用它可以让我们非常方便的搭建出 App 所需要的持久存储体系。
不过,大家是否知道在 CoreData 中还存在一个 Transformable 类型,它到底是个啥?应用场景有哪些?在最新的 SwiftData 中有没有对应物?对于开发者又有哪些“见雀张罗”的撸码陷阱和最佳实践呢?
在本篇博文中,您将学到如下内容:
- 概述
- 6. 派生自 NSObject 的自定义类
- 6.1 为 CoreData 中 Human 基类增加 skill 字段
- 6.2 实现 Skill 数据结构
- 总结
本系列文章一共包括将近 3w 枚机智而幽默的文字、详实的大段代码示例以及海量图片,定能让小伙伴们对 Transformable 类型的“驾驭”更加胸有成竹、胜券在握!
那还等什么呢?让我们马上开始 Transformable 大冒险吧!
Let’s go!!!😉
6. 派生自 NSObject 的自定义类
之前我们讨论了系统内置类、自定义结构等数据类型对于 Transformable 的支持,接下来,让我们再聊聊如何稳妥的处理自定义类的转换。
如若要我们自己的类也安全地支持 Transformable 类型,有两个条件:
- 必须派生自 NSObject 类;
- 必须遵守 NSSecureCoding 协议;
下面,我们就来实际撸码一回,看看具体的步骤是怎样的。
为了进一步拓展我们英雄和恶棍的战斗力,我们想为它们增加**技能(Skill)**这一超能力。我们这次希望用类的多态机制来适配不同种类 Human(英雄或恶棍)的 Skill。
6.1 为 CoreData 中 Human 基类增加 skill 字段
俗话说得好:“高楼平地起,基础不牢地动山摇”,我们首先要为 CoreData 中 Humam 基类增加支持 Skill 的字段:
从上图可以看到,我们主要做了以下几件事:
- 为 Human 托管基类增加 skill 字段,它的类型为 Transformable;
- 设置 skill 字段的 Transformer 属性为 SkillTransformer(待实现);
- 设置 skill 字段的 Custom Class 属性为 Skill(待实现);
这里再啰嗦几句,Transformable 字段的 Transformer 属性存放的是该字段实际类型到 Transformable 类型的转换器,而 Custom Class 则代表该字段的实际类型。
6.2 实现 Skill 数据结构
前面说过,我们希望英雄和恶棍的技能可以利用类的多态性来扩展,所以首先我们创建一个 Skill 抽象基类:
@objc(Skill)
public class Skill: NSObject, NSSecureCoding {let name: Stringinit(name: String) {if type(of: self) == Skill.self {fatalError("Skill 是抽象基类,禁止实例化!")}self.name = namesuper.init()}public class var supportsSecureCoding: Bool { true }public func encode(with coder: NSCoder) {coder.encode(name, forKey: "name")}required public init(coder: NSCoder) {name = coder.decodeObject(of: NSString.self, forKey: "name")! as Stringsuper.init()}
}
在 Skill 抽象基类的实现中,我们有几点需要注意:
- 抽象基类应该禁止实例化,因为 Swift 语言本身没有提供静态的禁止方法,所以我们只能通过动态判断来达成;
- Skill 内部应该存放所有子类共享的数据,比如 name;
- Skill 需要实现 supportsSecureCoding 属性并返回 true,表示它支持安全编码;
- Skill 应该实现 NSSecureCoding 协议所要求的两个方法,它们分别被用来做数据的编码和解码工作;
- Skill 的类名称需要用 @objc 关键字将 Swift 的类名暴露给 objc 层,否则运行会提示找不到名为 Skill 的类;
随后,我们需要分别实现英雄和恶棍的技能,它们应该是 Skill 的子类:
/// 英雄技能
@objc(HeroSkill)
class HeroSkill: Skill {static let glare = HeroSkill(name: "英雄怒视", justicePower: 109, color: .pink)let justicePower: Intlet color: Colorinit(name: String, justicePower: Int, color: Color) {self.justicePower = justicePowerself.color = colorsuper.init(name: name)}override class var supportsSecureCoding: Bool { true }public override func encode(with coder: NSCoder) {coder.encode(justicePower, forKey: "justicePower")let uiColor = UIColor(color)coder.encode(uiColor, forKey: "uiColor")super.encode(with: coder)}required public init(coder: NSCoder) {justicePower = coder.decodeInteger(forKey: "justicePower")let uiColor = coder.decodeObject(of: UIColor.self, forKey: "uiColor")!color = Color(uiColor: uiColor)super.init(coder: coder)}
}/// 恶棍技能
@objc(VillainSkill)
class VillainSkill: Skill {static let swallow = VillainSkill(name: "恶棍吞咽", evalPower: 121)let evalPower: Intinit(name: String, evalPower: Int) {self.evalPower = evalPowersuper.init(name: name)}override class var supportsSecureCoding: Bool { true }public override func encode(with coder: NSCoder) {coder.encode(evalPower, forKey: "evalPower")super.encode(with: coder)}required public init(coder: NSCoder) {evalPower = coder.decodeInteger(forKey: "evalPower")super.init(coder: coder)}
}
对于上面 HeroSkill 和 VillainSkill 两个子类,我们实现了它们各自具体的数据编码和解密功能,并为它们分别创建了一个实例(glare 和 swallow)。
回想一下,之前我们为 Human.skill 字段的 Custom Class 设置的是 Skill 基类,而不是具体的子类,这意味着具体 Skill 子类的编码和解密工作都会由系统自动帮你搞定,真是妙哉妙哉!
在下一篇博文中,我们将继续完成 Skill 到 Transformable 类型的转换,等着你们哦!
总结
在本篇博文中,我们初步介绍了派生自 NSObject 的自定义类如何支持 Transformable 的转换,你值得拥有!
感谢观赏,我们下一篇再会啦!😎