“一人得道,雨燕升天”:Swift 协议扩展助力 CoreData 托管类型(下)
概述
相信各位似秃非秃小码农们都同意,Swift 是一门现代化、安全且表现力足够丰富的语言。不过,它毕竟还是一种偏静态的语言,灵活性无法和 Python、ruby 之类的动态语言相提并论。
不过话虽如此,通过巧妙的一步步重构源代码,我们也可以用 Swift 完成之前貌似不可能完成的任务,所需的只是那么一丢丢耐心和执着而已。
在本篇博文中,您将学到如下内容:
- 一种很“硬”的解决方案
- 不想回到最初的样子
- 让编译器乖乖听话
希望在亲眼目睹本系列文章中 Swift 代码那循序渐进的重构和升华之后,小伙伴们倘若再遇到与此类似的语言设计问题,必能胸有成竹、胜券在握!
无需等待,Let‘s go!!!;)
5. 一种很“硬”的解决方案
对于前文中的问题,一种简单粗暴的解决方法是:强行让两种类型“蛮来生作”。
extension AchievementEvaluator {
static func queryAll(context: NSManagedObjectContext) throws -> [Evaluator] {
let request = Evaluator.fetchRequest() as! NSFetchRequest<Evaluator> // ⚠️ 强制转换
return try context.fetch(request)
}
}
如您所见,我们通过 Swift 强制类型转换语法,将 Evaluator.fetchRequest 实际的类型与 Evaluator 类型强行匹配。
虽然,这可以让编译器暂时闭嘴,但是也同时置我们自己于“刀山火海”之上!
上述代码的风险是:我们需要自行确保类型转换的安全性,若 Evaluator.fetchRequest()
实际返回的请求类型与 Evaluator
不匹配,将立即导致运行时发生崩溃。
6. 不想回到最初的样子
除了强行转换以外,我们还可以采用迂回战术:创建约束协议从而绕过编译器的“桎梏”。
首先,新建一个约束协议 Fetchable:
// 定义核心约束协议
protocol Fetchable: NSManagedObject {
static func fetchRequest() -> NSFetchRequest<Self>
}
接着,对原来的 AchievementEvaluator 协议定义稍作调整,让其关联类型遵守我们上面创建的约束协议:
// 原协议调整
protocol AchievementEvaluator {
associatedtype Evaluator: Fetchable & AchievementEvaluator // 新增 Fetchable 约束
static func queryAll(context: NSManagedObjectContext) throws -> [Evaluator]
}
随后,在 AchievementEvaluator 协议扩展中利用约束关系重新打造我们的 queryAll() 方法:
extension AchievementEvaluator where Evaluator: Fetchable {
static func queryAll(context: NSManagedObjectContext) throws -> [Evaluator] {
let request = Evaluator.fetchRequest() // ✅ 类型已明确为 NSFetchRequest<Evaluator>
return try context.fetch(request)
}
}
最后,让 Achv_NoBreakVictory 成就实体类遵守 Fetchable 约束协议即可:
extension Achv_NoBreakVictory: Fetchable, AchievementEvaluator {
typealias Evaluator = Achv_NoBreakVictory
}
虽然这种思路本身没什么问题,但可惜的是编译器还是会义无反顾的再次大声说“我恨你!”:
Protocol 'Fetchable' requirement 'fetchRequest()' cannot be satisfied by a non-final class ('Achv_NoBreakVictory') because it uses 'Self' in a non-parameter, non-result type position
通过上面的错误信息不难发现:大家貌似又回到了之前的“故步自封”—— 我们仍然需要让 Achv_NoBreakVictory 类加上 final 成为“孤家寡人”才能得偿所愿,这是我们不希望看到的。
所以,我们又该如何随遇而安呢?
7. 让编译器乖乖听话
其实,解决之道并没有想象的那么复杂,我们只需重新设计 Fetchable
协议即可。
我们的核心思想是:
机制 | 作用 |
---|---|
entityName 属性 |
动态获取实体名称,避免依赖自动生成的 fetchRequest()
|
手动构建 NSFetchRequest |
通过 NSFetchRequest<Self>(entityName:) 确保类型匹配 |
子类覆盖 entityName |
允许继承体系中的子类指定自己的实体名称 |
首先,通过 实体名称动态构建请求,绕过自动生成的 fetchRequest()
方法的限制:
protocol Fetchable: NSManagedObject {
static var entityName: String { get } // 要求实体提供名称
}
extension Fetchable {
static func fetchRequest() -> NSFetchRequest<Self> {
// 手动构建请求,确保类型安全
return NSFetchRequest<Self>(entityName: entityName)
}
}
接下来,我们只要让 Achv_NoBreakVictory 类乖巧的提供 entityName 名称即可:
extension Achv_NoBreakVictory: Fetchable, AchievementEvaluator {
static var entityName: String {
"Achv_NoBreakVictory"
}
typealias Evaluator = Achv_NoBreakVictory
}
现在,编译源代码将如您所愿,一切都毫无问题,整个世界清净了!
通过 动态实体名称 + 手动构建请求,既能保持类的可继承性,又能满足 Core Data 类型安全要求。其关键点在于:
- 通过
entityName
属性解耦实体名称与类型推断。 - 子类必须显式覆盖
entityName
以正确映射数据库实体。
然而,我们还可以更进一步。
观察上面 Achv_NoBreakVictory 类中对应 entityName 属性的代码可以发现:每个成就实体类的 entityName 就是它们自己类的名称。既然如此,为什么不把 entityName 也直接放到协议扩展中去呢?
extension AchievementEvaluator where Evaluator: Fetchable {
static var entityName: String {
"\(Self.self)"
}
static func queryAll(context: NSManagedObjectContext) throws -> [Evaluator] {
let request = Evaluator.fetchRequest() // ✅ 类型已明确为 NSFetchRequest<Evaluator>
return try context.fetch(request)
}
}
如上代码所示,我们将原本需要每个 AchievementEvaluator 实体类实现的 entityName 属性放到了 AchievementEvaluator 协议扩展中,大大减少了重复代码,这样的 DRY 和 KISS 谁能不爱呢?棒棒哒!
或者我们干脆彻底摆脱 entityName 属性的限制,直接将其嵌入到 Fetchable 协议扩展的 fetchRequest() 方法中,让实现百尺竿头、更入佳境:
extension Fetchable {
static func fetchRequest() -> NSFetchRequest<Self> {
// 手动构建请求,确保类型安全
return NSFetchRequest<Self>(entityName: "\(Self.self)")
}
}
至此,我们通过不断迭代重构,彻底摆脱了最初文章开头 CoreData 成就托管类实现的恼人纠缠,小伙伴们还不赶快给自己一个大大的赞吧!❤️
总结
在本篇博文中,我们借助于精心设计的 Fetchable 约束协议成功的摆脱了 Swift 协议扩展中的“磨搅讹绷”,小伙伴们值得拥有!
感谢观赏,再会啦!8-)