当前位置: 首页 > article >正文

CoreData 调试警告:多个 NSEntityDescriptions 声明冲突的解决

在这里插入图片描述

概述

目前在苹果生态 App 的开发中,CoreData 数据库仍然是大部分中小应用的优先之选。不过,运行时 CoreData 常常产生各种“絮絮叨叨”的警告不禁让初学的秃头小码农们云里雾里。

在这里插入图片描述

这不,对于下面这一大段 CoreData 警告,大家是否一眼便能拔丁抽楔,立即找出真正问题之所在呢?

warning: Multiple NSEntityDescriptions claim the NSManagedObject subclass ‘XXX’ so +entity is unable to disambiguate.
CoreData: warning: Multiple NSEntityDescriptions claim the NSManagedObject subclass ‘XXX’ so +entity is unable to disambiguate.

别急,我们马上为小伙伴们拨开云雾见青天。

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

  • 概述
  • 1. 喋喋不休:让人心烦意乱的警告
  • 2. 产生警告的原因:好心办坏事
  • 3. 和警告说拜拜
    • 3.1 抛弃 VictoryStage 返回对象
    • 3.2 指明 VictoryStage 对应的 NSEntityDescription
    • 3.3 虚拟托管对象
  • 总结

闲言少叙,Let‘s find out!!!😉


1. 喋喋不休:让人心烦意乱的警告

首先,让我们看一下示例中 CoreData 容器的创建代码:

struct PersistenceController {
    // CoreData 持久存储容器
    static let shared = PersistenceController()
    // Xcode 预览使用的 CoreData 容器,永远只在内存中玩耍😁
    static let preview = PersistenceController(inMemory: true)

    let container: NSPersistentCloudKitContainer

    init(inMemory: Bool = false) {
        container = NSPersistentContainer(name: "Store")
        if inMemory {
            container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
        }
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
    }
}

可以看到上面的代码平淡无奇。我们为 App 和 Xcode 预览分别创建了对应的持久存储容器(NSPersistentContainer),对于后者我们只需将存储路径指向一个“黑洞”(“/dev/null”)即可,这也是 Apple 官方标准的处理方法。

接着,简单介绍一下我们 App 数据库的表结构:

  • Project 托管类存放日常项目,它含有多个 VictoryStage 对象,它们之间是一对多的关系;
  • VictoryStage 托管类用来存放每个项目的执行情况;

运行 App 进入业务界面后,恼人的警告“大军”立即接踵而来:

warning: Multiple NSEntityDescriptions claim the NSManagedObject subclass ‘VictoryStage’ so +entity is unable to disambiguate.


CoreData: warning: Multiple NSEntityDescriptions claim the NSManagedObject subclass ‘VictoryStage’ so +entity is unable to disambiguate.


warning: ‘VictoryStage’ (0x600003508630) from NSManagedObjectModel (0x600002128a50) claims ‘VictoryStage’.


CoreData: warning: ‘VictoryStage’ (0x600003508630) from NSManagedObjectModel (0x600002128a50) claims ‘VictoryStage’.
warning: ‘VictoryStage’ (0x6000035048f0) from NSManagedObjectModel (0x60000217d400) claims ‘VictoryStage’.


CoreData: warning: ‘VictoryStage’ (0x6000035048f0) from NSManagedObjectModel (0x60000217d400) claims ‘VictoryStage’.
error: +[VictoryStage entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass


CoreData: error: +[VictoryStage entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass

仔细观察警告内容,它们大致在述说着同一个 NSManagedObject 托管类型被多个 NSEntityDescription 所声明,这个托管类型就是上面的 VictoryStage 类。

2. 产生警告的原因:好心办坏事

上面警告只是出现在 Xcode 的调试控制台里,并没有引起 App 执行的中断。貌似它的严重性并没有那么大。但是,要注意在这种情况下 CoreData 的存储操作可能会产生未定义行为,所以更需严肃对待。

因为警告发生在 SwiftUI 的视图中,所以我们可以立即采用“最小系统法”让罪魁祸首“原形毕露”。

很快,我们就将产生的警告定位到如下方法中:

func calcCurrentVStage() throws -> VictoryStage {
    let req = VictoryStage.fetchRequest()
    req.predicate = .init(format: "project == %@", self)
    req.sortDescriptors = [.init(keyPath: \VictoryStage.end, ascending: false)]
    req.fetchLimit = 1
    
    // 产生一个 VictoryStage 对象用来临时保存一次性结果
    let dumpContext = PersistenceController.preview.container.viewContext
    let dumpVStage = VictoryStage(context: dumpContext)
    
    dumpVStage.start = currentStageStart!
    dumpVStage.end = nil
    dumpVStage.daysElapsed = 0
    
    if let recentVStage = try managedObjectContext?.fetch(req).first {
        if let lastSetback = newestSetbacks(1).first, lastSetback.date! < recentVStage.realEnd  {
            return recentVStage
        } else {
            return dumpVStage
        }
    } else {
        return dumpVStage
    }
}

该方法主要做了以下几件事:

  • 计算 Project 当前的 VictoryStage 对象;
  • 当无法找到当前 VictoryStage 对象时,我们会产生一个临时 VictoryStage 对象,然后返回它;
  • 我们希望该临时 VictoryStage 对象永远驻留在内存里,所以将它的上下文设置为前面为 Xcode 预览特供的 “In Memory Only” 上下文;

我们的本意是好的,可惜好心办了坏事。

在这里插入图片描述

考虑一下,当执行 calcCurrentVStage() 方法后,当前我们就同时有了两个 CoreData 持久存储容器(Container):shared 和 preview。其中,每个 Container 中都会有一个对应 VictoryStage 的实体描述对象(NSEntityDescription) ,它们重复了!这就是博文开头警告的根本原因!

再回顾一下上面的代码:

let dumpContext = PersistenceController.preview.container.viewContext
let dumpVStage = VictoryStage(context: dumpContext)

可以看到,我们创建的 dumpVStage 对象无法确定是对应 shared 或 preview 哪个容器中的 VictoryStage 描述符,这自然让 CoreData 在运行时方寸大乱。

那么,我们又该何去何从呢?

3. 和警告说拜拜

3.1 抛弃 VictoryStage 返回对象

一种解决办法是完全抛弃临时的 VictoryStage 对象,只返回一个包含 VictoryStage 对象属性的元组,毕竟我们也不是真的要读取 VictoryStage 对象,而只是想访问它内部属性的值。

3.2 指明 VictoryStage 对应的 NSEntityDescription

除此之外,我们还可以向 CoreData 运行时(Runtime)“忠告善道”指定对应的 NSEntityDescription 对象:

let previewContainer = PersistenceController.preview.container        
let vstageEntity = previewContainer.managedObjectModel.entitiesByName["VictoryStage"]!
let dumpVStage = VictoryStage(entity: vstageEntity, insertInto: previewContainer.viewContext)

在上面的代码中,我们利用托管类的另一个构造器 init(entity:insertInto:) 为 dumpVStage 临时对象指明了对应的 entity 对象,并同时将其与 previewContainer.viewContext 上下文相绑定。

3.3 虚拟托管对象

既然我们的目的是创建一个只驻留于内存中的 VictoryStage 临时对象,那么为什么不再大胆一些,直接忽略 preview 容器,只借助 shared 创一个虚拟托管对象呢?

所谓虚拟托管对象就是永远不会保存到持久存储中的对象,利用这一点我们可以轻松重制上面的 dumpVStage 临时对象了:

let vstageEntity = PersistenceController.shared.container.managedObjectModel.entitiesByName["VictoryStage"]!
let dumpVStage = VictoryStage(entity: vstageEntity, insertInto: nil)

如您所见,现在 dumpVStage 对象将不会与任何上下文绑定,所以它永远不会被存储到持久数据库中。


注意,我们不能这样直接创建 VictoryStage 虚拟托管对象:

let dumpVStage = VictoryStage()

虽然编译没有任何问题,但运行会妥妥的崩溃哦,切记切记。


至此,我们完全解惑了博文开头那个警告,小伙伴们想必再遇到类似的问题一定能够胸有成竹啦,棒棒哒!💯

总结

在本篇博文中,我们讨论了 “Multiple NSEntityDescriptions claim the NSManagedObject subclass” 这一多个 NSEntityDescriptions 声明冲突警告产生的原因,并给出多个解决方案。

感谢观赏,再会啦!😎


http://www.kler.cn/a/589729.html

相关文章:

  • 数模AI使用教程(新) 2025.3.17
  • Windows安全日志Defender 的配置被修改5007
  • Python数据可视化——生成数据(一)
  • Python基于Django和协同过滤算法实现电影推荐系统功能丰富版
  • 跟着AI复习一下pytorch原理和操作
  • OpenCV计算摄影学(22)将输入的彩色图像转换为两种风格的铅笔素描效果函数pencilSketch()
  • 嵌入式硬件篇---龙芯GPIO控制
  • CTF WEB题
  • 每日一题--进程与协程的区别
  • 【在校课堂笔记】Python 第5节课 总结
  • axios 和 fetch异同点
  • Java继承与反思,单例模式与静态的思考
  • 【redis】Jedis 操作 Redis 基础指令(下)
  • Mysql中创建表时的约束条件
  • 力扣刷题——2181.合并零之间的节点
  • Java中的GC是什么?
  • STM32U575RIT6单片机(四)
  • 基于WebRTC与P2P技术,嵌入式视频通话EasyRTC实现智能硬件音视频交互,适配Linux、ARM、RTOS、LiteOS
  • 深度学习处理时间序列(1)
  • 嵌入式学习笔记-C语言知识点:栈的作用,C语言函数参数的入栈顺序,C++ 拷贝构造函数,数组名和指针的区别与联系,指针运算,指针和引用