Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)

news2025/6/12 21:41:19

在这里插入图片描述

概述

在 Swift 开发语言中,各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。

在这里插入图片描述

不过,在涉及到多个子类派生于基类进行多态模拟的场景下,稍不留神可能就会产生恢诡谲怪的错误。这是怎么回事?又该如何解决呢?

在本篇博文中,您将学到如下内容:

  • 概述
  • 3. 一种略显麻烦的解决
    • 3.1 步骤 1:修改 `Fetchable` 协议,增加类型过滤支持
    • 3.2 步骤 2:在 Core Data 模型中为基类 `Achievement` 添加实体类型字段
    • 3.3 步骤 3:修改协议扩展,移除强制转换
  • 4. 一个方法换一方清净
  • 总结

在学完本课后,相信小伙伴们都会在撸码实战中重新找回自信,并向更深一层的内功修为奋勇前进!

那还等什么呢?让我们马上开始 Swift 精进之旅吧!
Let’s go!!!😉


3. 一种略显麻烦的解决

通过之前的讨论,我们可以很快给出应对措施,那就是通过 实体类型过滤 + 类型安全转换 解决这种混合类型问题:

3.1 步骤 1:修改 Fetchable 协议,增加类型过滤支持

import CoreData

protocol Fetchable: Achievement {
    static var entityTypeKey: String { get } // 每个子类定义唯一标识
}

extension Fetchable {
    static func fetchRequest() -> NSFetchRequest<Self> {
        let request = NSFetchRequest<Self>(entityName: "\(Self.self)")
        // 添加类型过滤谓词,确保仅返回当前子类的实例
        request.predicate = NSPredicate(format: "entityType == %@", entityTypeKey)
        return request
    }
}

3.2 步骤 2:在 Core Data 模型中为基类 Achievement 添加实体类型字段

  1. 在 Xcode 的 Core Data 模型编辑器中:
    • 选中 Achievement 实体
    • 添加属性 entityType(类型为 String,非可选,设置默认值为空)
  2. 在每个子类初始化时自动设置 entityType 字段正确的值:
// 基类扩展
extension Achievement {
    override func awakeFromInsert() {
        super.awakeFromInsert()
        // 子类需覆盖 entityTypeKey
        self.entityType = (self as! any Fetchable).type(of: self).entityTypeKey
    }
}

// 子类实现
extension Achv_NoBreakVictory: Fetchable {
    static let entityTypeKey = "noBreakVictory"
}

extension Achv_MultipleSerialVictories: Fetchable {
    static let entityTypeKey = "multipleSerialVictories"
}

3.3 步骤 3:修改协议扩展,移除强制转换

extension AchievementEvaluator where Evaluator: Fetchable {
    static func queryAll(context: NSManagedObjectContext) throws -> [Evaluator] {
        let request = Evaluator.fetchRequest()
        let results = try context.fetch(request)
        // 安全过滤 + 转换
        return results.compactMap { $0 as? Evaluator }
    }
}

在上面的解决方案中,我们主要做了如下几点改变:

机制作用
entityType 字段显式标记实例所属子类类型,避免混合查询
awakeFromInsert自动为每个新实例设置类型标识
谓词过滤 entityType == %@确保 fetchRequest 仅返回当前子类实例
compactMap + as?二次类型校验,防止意外数据污染

其实,我们还可以利用 索引优化 做进一步改进:为 entityType 字段添加数据库索引,提升查询效率。


关于 CoreData 索引机制的详细研究,请小伙伴们移步如下链接观赏精彩的内容:

  • 探究 CoreData 使用索引(Index)机制加速查表究竟如何实现?

4. 一个方法换一方清净

不过话又说回来,上面的解决方法仍有一些让人难以忍受的地方:

  • 要在表中新增一个 entityType 字段;
  • 需要介入托管对象的 awakeFromInsert 方法做额外的操作;

那么,有没有更简单的办法呢?

答案是肯定的!

回到 Fetchable 协议扩展的实现中,既然原来的 fetchRequest() 方法根本不会被调用,我们不如另外实现一个与它同名的重载方法:

extension Fetchable {
    static func fetchRequest(entityName: String) -> NSFetchRequest<Self> {
        NSFetchRequest<Self>(entityName: "\(Self.self)")
    }
}

可以看到,在新的 fetchRequest(entityName: String) 方法里,我们明确使用了子类的名称来创建正确的查询请求。

最后,再回到 AchievementEvaluator 的协议扩展里,替换原来两个方法中的 fetchRequest() 为新的 fetchRequest(entityName: String) 方法:

extension AchievementEvaluator where Evaluator: Fetchable {
    
    static func calcCount(context: NSManagedObjectContext) throws -> Int {
        let req = Evaluator.fetchRequest(entityName: "\(Self.self)")
        return try context.count(for: req)
    }
    
    static func queryAll(context: NSManagedObjectContext) throws -> [Evaluator] {
        let req = Evaluator.fetchRequest(entityName: "\(Self.self)")
        req.sortDescriptors = [
            .init(keyPath: \Achievement.orderNumber, ascending: true)
        ]
        // return try context.fetch(req) as! [Evaluator]
        return try context.fetch(req)
    }
}

如此一来,使用新的 fetchRequest(entityName: String) 方法不仅在调用时消除了成就基类和多个实体成就子类之间的转换歧义,而且在 queryAll() 方中,原本对于查询结果的强制转换代码也已不再需要,这进一步简化了代码逻辑并提高了运行安全,棒棒哒!💯

总结

在本篇博文中,我们介绍了利用 Swift 协议扩展解决 CoreData 基类 + 子类多态场景在运行时发生类型不匹配崩溃的两种解决方案,并逐一做了深入地讨论。

感谢观赏,再会啦!😎

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

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

相关文章

无法与IP建立连接,未能下载VSCode服务器

如题&#xff0c;在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈&#xff0c;发现是VSCode版本自动更新惹的祸&#xff01;&#xff01;&#xff01; 在VSCode的帮助->关于这里发现前几天VSCode自动更新了&#xff0c;我的版本号变成了1.100.3 才导致了远程连接出…

基于Flask实现的医疗保险欺诈识别监测模型

基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施&#xff0c;由雇主和个人按一定比例缴纳保险费&#xff0c;建立社会医疗保险基金&#xff0c;支付雇员医疗费用的一种医疗保险制度&#xff0c; 它是促进社会文明和进步的…

SCAU期末笔记 - 数据分析与数据挖掘题库解析

这门怎么题库答案不全啊日 来简单学一下子来 一、选择题&#xff08;可多选&#xff09; 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘&#xff1a;专注于发现数据中…

vscode(仍待补充)

写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh&#xff1f; debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八

现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet&#xff0c;点击确认后如下提示 最终上报fail 解决方法 内核升级导致&#xff0c;需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…

(二)TensorRT-LLM | 模型导出(v0.20.0rc3)

0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述&#xff0c;后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作&#xff0c;其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…

CMake基础:构建流程详解

目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…

visual studio 2022更改主题为深色

visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中&#xff0c;选择 环境 -> 常规 &#xff0c;将其中的颜色主题改成深色 点击确定&#xff0c;更改完成

关于nvm与node.js

1 安装nvm 安装过程中手动修改 nvm的安装路径&#xff0c; 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解&#xff0c;但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后&#xff0c;通常在该文件中会出现以下配置&…

使用分级同态加密防御梯度泄漏

抽象 联邦学习 &#xff08;FL&#xff09; 支持跨分布式客户端进行协作模型训练&#xff0c;而无需共享原始数据&#xff0c;这使其成为在互联和自动驾驶汽车 &#xff08;CAV&#xff09; 等领域保护隐私的机器学习的一种很有前途的方法。然而&#xff0c;最近的研究表明&…

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端

&#x1f31f; 什么是 MCP&#xff1f; 模型控制协议 (MCP) 是一种创新的协议&#xff0c;旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议&#xff0c;它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版​分享

平时用 iPhone 的时候&#xff0c;难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵&#xff0c;或者买了二手 iPhone 却被原来的 iCloud 账号锁住&#xff0c;这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…

【网络安全产品大调研系列】2. 体验漏洞扫描

前言 2023 年漏洞扫描服务市场规模预计为 3.06&#xff08;十亿美元&#xff09;。漏洞扫描服务市场行业预计将从 2024 年的 3.48&#xff08;十亿美元&#xff09;增长到 2032 年的 9.54&#xff08;十亿美元&#xff09;。预测期内漏洞扫描服务市场 CAGR&#xff08;增长率&…

DAY 47

三、通道注意力 3.1 通道注意力的定义 # 新增&#xff1a;通道注意力模块&#xff08;SE模块&#xff09; class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)

目录 1.TCP的连接管理机制&#xff08;1&#xff09;三次握手①握手过程②对握手过程的理解 &#xff08;2&#xff09;四次挥手&#xff08;3&#xff09;握手和挥手的触发&#xff08;4&#xff09;状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…

UE5 学习系列(三)创建和移动物体

这篇博客是该系列的第三篇&#xff0c;是在之前两篇博客的基础上展开&#xff0c;主要介绍如何在操作界面中创建和拖动物体&#xff0c;这篇博客跟随的视频链接如下&#xff1a; B 站视频&#xff1a;s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…

【JVM】- 内存结构

引言 JVM&#xff1a;Java Virtual Machine 定义&#xff1a;Java虚拟机&#xff0c;Java二进制字节码的运行环境好处&#xff1a; 一次编写&#xff0c;到处运行自动内存管理&#xff0c;垃圾回收的功能数组下标越界检查&#xff08;会抛异常&#xff0c;不会覆盖到其他代码…

聊聊 Pulsar:Producer 源码解析

一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台&#xff0c;以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中&#xff0c;Producer&#xff08;生产者&#xff09; 是连接客户端应用与消息队列的第一步。生产者…

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止

<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet&#xff1a; https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…

UDP(Echoserver)

网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法&#xff1a;netstat [选项] 功能&#xff1a;查看网络状态 常用选项&#xff1a; n 拒绝显示别名&#…