You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Core Data实体关系触发NSUnknownKeyException常规排查无效求助

问题描述

我开发的应用使用Core Data实现数据持久化,采用MVVM架构以方便测试。
开发环境为搭载M1 Max芯片的MacBook Pro,运行macOS 12.4系统与Xcode 14 beta 2。
此前项目运行一切正常,直到我尝试在数据模型中新增一个与其他实体存在多对一relationship的entity:实例化该实体时会抛出指向关系定义属性的NSUnknownKeyException,我从模型中删除该关系后重新运行测试,意外发现错误仍然存在,尝试以下所有操作后均无法解决问题:

  • 清理构建文件夹
  • 删除派生数据,重启Xcode
  • 重命名报错实体(错误仍存在,仅随实体名变化对应更新)
  • 删除问题实体后重新添加到数据模型
  • 关闭代码生成功能,手动编写NSManagedObject子类
  • 检查Core Data模型XML文件,无异常残留属性可引发该问题
  • 从模拟器中删除应用
  • 抹除模拟器所有设置后重启
  • 卸载后重新安装Xcode

我目前唯一未尝试的方案是使用Xcode 13运行代码(由于是新设备,我直接安装了beta版本,这可能是问题诱因),后续会尝试该方案。
当前判断是和Core Data模型、实体间关系相关的异常缓存存在于无法定位的路径,也有可能是beta版工具链本身的bug,该无法清除的错误导致开发无法继续推进,希望能得到相关排查思路和指导。

相关参考截图

  • Prescription实体的Xcode模型界面
    模型编辑器中的Prescription实体
  • Procedure实体的Xcode模型界面
    模型编辑器中的Procedure实体
  • Prescription实体对应的.xcdatamodeld XML内容
    .xcdatamodeld文件中Prescription实体相关XML片段
  • 测试过程中的报错信息
    测试过程中Prescription对象相关报错

报错托管对象子类扩展(便利方法实现)

extension Prescription {

    // MARK: - INTERNAL TYPES

    enum PointOfCareContext: String, CustomStringConvertible {

        case premedication, perioperative, postoperative, hospitalised

        var description: String {
            return self.rawValue.capitalized
        }
    }

    // MARK: - PROPERTIES

    var drug: Drug {
        get { guard let drug = cd_drug else {
            fatalError("Persistent store corrupted: Prescription is missing relationship to Drug.")
        }
            return drug
        }
        set { cd_drug = newValue }
    }
    
    var timePrescribed: Date {
        get { guard let timePrescribed = cd_timePrescribed else {
            fatalError("Persistent store corrupted: Prescription is missing timePrescribed.")
        }
            return timePrescribed
        }
        set { cd_timePrescribed = newValue }
    }

    /// When the drug should be administered
    /// Passing nil to this property indicates the drug should be given asap (so will return current Date)
    var timeScheduled: Date? {
        get { guard let timeScheduled = cd_timeScheduled else {
            return Date.now
        }
            return timeScheduled
        }
        set { cd_timeScheduled = newValue }
    }

    /// When the drug was administered
    /// Nil indicates not given yet
    var timeAdministered: Date? {
        get { return cd_timeAdministered }
        set { cd_timeScheduled = newValue }
    }

    var doseRate: Measurement<UnitDensity> {
        get {
            if (cd_doseRate != 0) {
                fatalError("Persistent store corrupted: Prescription doseRate == 0.")
            }
            return Measurement(value: cd_doseRate, unit: .milligramsPerKilogram)
        }
        set {
            cd_doseRate = newValue.converted(to: .milligramsPerKilogram).value
        }
    }

    var dose: Measurement<UnitMass> {
        get {
            return Measurement(value: cd_dose, unit: .milligrams)
        }
        set {
            cd_doseRate = newValue.converted(to: .milligrams).value
        }
    }

    var doseVolume: Measurement<UnitVolume> {
        get {
            return Measurement(value: cd_doseVolume, unit: .milliliters)
        }
        set {
            cd_doseVolume = newValue.converted(to: .milliliters).value
        }
    }

    var context: PointOfCareContext {
        get {
            guard let contextValue = cd_context, let context = PointOfCareContext(rawValue: contextValue) else {
                fatalError("Persistent store corrupted: Prescription has an invalid administration context [\(cd_context ?? "")]")
            }
            return context
        }
        set {
            cd_context = newValue.rawValue
        }
    }
}

测试对应的视图模型代码

extension ProcedurePlanView {
    class ViewModel: NSObject, ObservableObject, NSFetchedResultsControllerDelegate {
        // swiftlint:disable:next nesting
        typealias Dependencies = HasParameterController & HasPersistence & HasViewFactory

        private let parameterController: ParameterController
        private let persistenceService: PersistenceService
        private let viewFactory: ViewFactory
        private let frc: NSFetchedResultsController<Prescription>

        let patient: Patient
        let procedure: Procedure

        // MARK: - TabOne conformance
        @Published var clinician: String = ""
        @Published var anaesthetist: String = ""
        @Published var assistant: String = ""
        @Published var history: String = ""
        @Published var tasks: String = ""
        @Published var complications: String = ""
        @Published var mucousMembraneColor: MucousMembraneColor?
        @Published var asaGrade: ASAGrade?
        @Published var cannulaPosition: CannulaPosition?
        @Published var cannulaSide: Side?
        @Published var cannulaGauge: PeripheralCannulaGauge?
        @Published var auscultation: String = ""
        @Published var temperament: Double?
        @Published var airwayType: AirwayType?
        @Published var airwaySize: String?
        @Published var airwayCuffStatus: Bool?
        @Published var breathingSystem: BreathingSystemType?
        @Published var prescriptions: [Prescription]?

        // MARK: - INIT
        init(procedure: Procedure? = nil, patient: Patient, dependencies: Dependencies) {
            self.persistenceService = dependencies.persistenceService
            self.parameterController = dependencies.parameterController
            self.viewFactory = dependencies.viewFactory

            var currentProcedure: Procedure?
            if let procedure {
                currentProcedure = procedure
            } else {
                currentProcedure = Procedure(context: persistenceService.viewContext)
                currentProcedure!.patient = patient
            }

            let frc = ProcedurePlanView.ViewModel.createPremedFRCUsing(
                patient,
                procedure: currentProcedure!,
                context: dependencies.persistenceService.viewContext
            )

            self.frc = frc
            self.patient = patient
            self.procedure = currentProcedure!
            self.prescriptions = ProcedurePlanView.ViewModel.performInitialFetchFor(frc)
            
            super.init()
            self.frc.delegate = self
        }

        // MARK: - PUBLIC METHODS
        func makeParameterGridView() -> some View {
            viewFactory.makeParameterGridView(patient: patient)
        }

        func resetForm() {
            clinician.removeAll()
            anaesthetist.removeAll()
            assistant.removeAll()
            history.removeAll()
            tasks.removeAll()
            complications.removeAll()

            mucousMembraneColor = nil
            asaGrade = nil
            cannulaPosition = nil
            cannulaSide = nil
            cannulaGauge = nil
            auscultation.removeAll()
            temperament = nil
            airwayType = nil
            airwaySize = nil
            airwayCuffStatus = nil
            breathingSystem = nil
        }

        func save() {
            procedure.patient = patient
            procedure.clinican = clinician
            procedure.anaesthetist = anaesthetist
            procedure.assistant = assistant
            procedure.history = history
            procedure.tasks = tasks
            procedure.complications = complications
            procedure.asaGrade = asaGrade
            Logger.viewModel.warning("Incomplete implementaton of cannula data in Procedure managed object and VM")

            _ = Observation.create(
                in: persistenceService.viewContext,
                patient: patient,
                parameter: parameterController.getParameterWith(name: .mucousMembraneColor),
                stringValue: mucousMembraneColor?.rawValue ?? "Unknown"
            )

            do {
                try persistenceService.viewContext.saveIfNeeded()
            } catch {
                fatalError(
                    "Unable to save context \(persistenceService.viewContext) to store due to error: \(error.localizedDescription)"
                )
            }
        }
        
        // MARK: - PRIVATE METHODS
        private static func createPremedFRCUsing(_ patient: Patient, procedure: Procedure, context: NSManagedObjectContext) -> NSFetchedResultsController<Prescription> {
            let request = Prescription.fetchRequest()
            request.predicate = NSPredicate(format: "procedure == %@", procedure)
            request.sortDescriptors = [NSSortDescriptor(keyPath: \Prescription.cd_drug?.cd_nameGeneric, ascending: false)]

            let frc = NSFetchedResultsController(
                fetchRequest: request,
                managedObjectContext: context,
                sectionNameKeyPath: nil,
                cacheName: nil
            )
            
            return frc
        }
        
        private static func performInitialFetchFor(_ frc: NSFetchedResultsController<Prescription>) -> [Prescription] {
            do {
                try frc.performFetch()
            } catch {
                Logger.persistenceService.critical(
                    "FRC failed to fetch Drugs from persistent store: \(error.localizedDescription)"
                )
            }

            return frc.fetchedObjects ?? []
        }

        // MARK: - NSFetchedResultsControllerDelegate conformance
        internal func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
            let newPrescriptions = controller.fetchedObjects as? [Prescription] ?? []
            prescriptions = newPrescriptions
        }
    }
}

内容的提问来源于stack exchange,提问作者rustproofFish

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 21:45:33