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

Core Data中使用ValueTransformer保存数组数据重启后丢失问题排查

问题:Core Data中使用ValueTransformer保存UUID数组,重启后数据丢失

配置ValueTransformer保留数组对象,创建数据后刷新可正常查看,但关闭重启程序后,habitData数组数据丢失,其余数据完好,重启后habitData始终返回nil。


相关代码

启动代码

func createDatabase() {
    ValueTransformer.setValueTransformer(UUIDTransformer(), forName: NSValueTransformerName("UUIDTransformer"))
    ValueTransformer.setValueTransformer(TheCategoriesTransformer(), forName: NSValueTransformerName("TheCategoriesTransformer"))
    
    do { theDailyData = try contextDataCore.fetch(The_daily_data.fetchRequest()) } catch { }
    do { theHabitList = try contextDataCore.fetch(The_habit_list.fetchRequest()) } catch { }
    do { thePhotoData = try contextDataCore.fetch(The_photos.fetchRequest()) } catch { }
}

转换器代码

class UUIDTransformer: ValueTransformer {
    override func transformedValue(_ value: Any?) -> Any? {
        guard let uuid = value as? [UUID] else { return nil }

        do {
            let data = try NSKeyedArchiver.archivedData(withRootObject: uuid, requiringSecureCoding: true)
            return data
        } catch {
            return nil
        }

    }

    override func reverseTransformedValue(_ value: Any?) -> Any? {
        guard let data = value as? Data else { return nil }

        do {
            let uuid = try NSKeyedUnarchiver.unarchivedObject(ofClass: NSArray.self, from: data) as! [UUID]
            return uuid
        } catch {
            return nil
        }

    }
}

class TheCategoriesTransformer: ValueTransformer {
    override func transformedValue(_ value: Any?) -> Any? {
        guard let uuid = value as? [String] else { return nil }

        do {
            let data = try NSKeyedArchiver.archivedData(withRootObject: uuid, requiringSecureCoding: true)
            return data
        } catch {
            return nil
        }

    }

    override func reverseTransformedValue(_ value: Any?) -> Any? {
        guard let data = value as? Data else { return nil }

        do {
            let categories = try NSKeyedUnarchiver.unarchivedObject(ofClass: NSArray.self, from: data) as! [String]
            return categories
        } catch {
            return nil
        }

    }
}

创建数据代码

func newJournalData(theDate: Date, theHabitID: UUID? = nil, theSubject: String? = nil, theJournal: String? = nil, theMind: String? = nil, theWeather: String = "NA") {
    let repairDate = Calendar.current.dateComponents([.year, .month, .day], from: theDate)
    let freshDate = Calendar.current.date(from: repairDate)
    
    var newHabitID: [UUID] = []
    if theHabitID != nil { newHabitID.append(theHabitID!) }
    
    var theImpactCount = 0
    var theImpactTotal = 0
    
    let findHabitImpact = theHabitList.first(where: { $0.theID == theHabitID })
    
    for countX in theHabitList {
        switch countX.impact {
        case 0: theImpactTotal += 1
        case 1: theImpactTotal += 5
        case 2: theImpactTotal += 10
        default: theImpactTotal += 0
        }
    }
    
    if findHabitImpact != nil {
        switch findHabitImpact?.impact {
        case 0: theImpactCount += 1
        case 1: theImpactCount += 5
        case 2: theImpactCount += 10
        default: theImpactCount += 0
        }
    }
    
    let finalImpact = Int((Double(theImpactCount) / Double(theImpactTotal)) * 100)
    
    let newJournalData = The_daily_data(context: contextDataCore)
    newJournalData.date = freshDate
    newJournalData.habitData = newHabitID
    newJournalData.theSubject = theSubject
    newJournalData.journal = theJournal
    newJournalData.yourMind = theMind
    newJournalData.theWeather = theWeather
    newJournalData.percent = Int16(finalImpact)
    
    do { try contextDataCore.save(); createDatabase() } catch { print("Error") }
}

问题原因及解决方法

核心问题

UUIDTransformer的反向转换方法中,仅用NSArray.self作为解归档的类,无法验证数组内UUID元素的安全编码类型,导致程序重启后解归档失败,返回nil。

修复步骤

  1. 修改UUIDTransformer的反向转换方法
    使用ofClasses方法同时指定数组和元素的类型,确保UUID能被正确解析:

    override func reverseTransformedValue(_ value: Any?) -> Any? {
        guard let data = value as? Data else { return nil }
        
        do {
            let uuidArray = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSArray.self, UUID.self], from: data) as? [UUID]
            return uuidArray
        } catch {
            print("UUID数组解归档失败: \(error)")
            return nil
        }
    }
    
  2. 调整转换器注册时机
    必须在Core Data上下文初始化之前注册转换器,建议把注册代码放到AppDelegate的启动方法或Core Data栈初始化代码中,避免上下文创建后注册导致转换器未生效。

  3. 验证Core Data模型配置
    检查The_daily_data实体的habitData属性:

    • 类型设为Transformable
    • Value Transformer Name填写UUIDTransformer(与代码中注册的名称完全一致)
    • 勾选Allows External Storage(优化大数组存储)

额外优化建议

  • 避免使用强制解包as!,改用可选绑定as?防止崩溃
  • 对TheCategoriesTransformer做同样修改,用[NSArray.self, NSString.self]指定类型提升稳定性:
    override func reverseTransformedValue(_ value: Any?) -> Any? {
        guard let data = value as? Data else { return nil }
        
        do {
            let categories = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSArray.self, NSString.self], from: data) as? [String]
            return categories
        } catch {
            print("分类数组解归档失败: \(error)")
            return nil
        }
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 19:27:22