App crashes when trying to encode custom objects that conform to codable protocol and I can't figure out why, any thoughts?

huangapple go评论72阅读模式
英文:

App crashes when trying to encode custom objects that conform to codable protocol and I can't figure out why, any thoughts?

问题

Here's the translated content without code:

我有两个自定义对象,Exercises(练习)和Workouts(锻炼)。Exercises符合Codable协议,可以使用JSONEncoder进行编码和解码,没有问题。我有一个Exercise对象的数组进行了归档。问题出现在Workout对象上。由于某种原因,使用JSON编码器无法对其进行编码。我试图编码一个包含两个Workout对象数组的对象。

对于这个崩溃有什么想法吗?我之前在Workout对象中手动实现了Codable,但它会在这一行崩溃:

let exercisesData = try NSKeyedArchiver.archivedData(withRootObject: exercises, requiringSecureCoding: true)

然后我意识到Workout的所有属性已经符合Codable,所以我将其注释掉了。以下是代码。

当我移除Workout对象中的手动Codable实现时,我收到的错误消息是:

无法写入数据,因为格式不正确

(我使用JSONEncoder对一个包含两个Workout对象数组的对象进行编码)。如果我保留手动实现的注释,它会在这一行崩溃:

let exercisesData = try NSKeyedArchiver.archivedData(withRootObject: exercises, requiringSecureCoding: true)

并显示以下消息:

Exercise未实现methodSignatureForSelector -- 有麻烦了 Unrecognized selector Exercise replacementObjectForKeyedArchiver

我是Swift编程的新手,我在按照一些设置指南进行操作。几周前,对于更简单的数据,这对我起作用。

以上是您提供的代码的翻译。如果您需要进一步的帮助,请告诉我。

英文:

So I have two custom objects, Exercises and Workouts. Exercises conforms to Codable and is able to be encoded and decoded just fine using JSONEncoder. I have an array of Exercise objects that I archive. The issue is with the Workout object. For some reason it does not get encoded using the JSON encoder. I am trying to encode an object which contains two arrays of Workout objects.

Any thoughts as to why this is crashing? I earlier had the manual implementation of Codable in Workout object but it would crash at this line:

let exercisesData = try NSKeyedArchiver.archivedData(withRootObject: exercises, requiringSecureCoding: true)

Then I realized all attributes of Workout already conform to Codable so I commented it out. Code is below.

With the manual implementation of Codable in the Workout object removed, the error message I get is:

>The data couldn't be written because it isn't in the correct format

(again I'm using JSONEncoder to encode an object with 2 arrays of Workout objects).
If I leave the manual implementation uncommented, it crashes at this line:

let exercisesData = try NSKeyedArchiver.archivedData(withRootObject: exercises, requiringSecureCoding: true)

with the message:

>Exercise does not implement methodSignatureForSelector -- trouble ahead Unrecognized selector Exercise replacementObjectForKeyedArchiver

I'm new to Swift programming and I was following some guides on how to set this up. It has worked for me a few weeks ago with simpler data.

class Exercise: Equatable, Codable, NSCopying {
    var name: String
    var muscleGroup: String
    var description: String
    var image: UIImage
    var logs = [(weight: String, reps: String, complete: Bool)]()
    private let isPrimary: Bool
    
    //Init function
    init(name: String, muscleGroup: String, description: String, image: UIImage, isPrimary: Bool = false) {
        self.name = name //Treat this as unique
        self.muscleGroup = muscleGroup
        self.description = description
        self.image = image
        self.isPrimary = isPrimary
    }
    
    //Override equatable to check for uniqueness
    static func == (lhs: Exercise, rhs: Exercise) -> Bool {
        return lhs.name.lowercased() == rhs.name.lowercased()
    }
    
    public enum CodingKeys: String, CodingKey {
        case name
        case muscleGroup
        case description
        case image
        case logs
        case isPrimary
    }
    
    //Decoder function implementation
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        // Decoding the name property
        let nameData = try container.decode(Data.self, forKey: .name)
        self.name = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(nameData) as? String ?? "No Exercise Name"

        // Decoding the muscleGroup property
        let muscleGroupData = try container.decode(Data.self, forKey: .muscleGroup)
        self.muscleGroup = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(muscleGroupData) as? String ?? "Other"

        // Decoding the description property
        let descriptionData = try container.decode(Data.self, forKey: .description)
        self.description = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(descriptionData) as? String ?? "None"
        
        // Decoding the image property
        let imageData = try container.decode(Data.self, forKey: .image)
        self.image = try (NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(imageData) as? UIImage)!
        
        // Deocding the logs property
        let logsData = try container.decode(Data.self, forKey: .logs)
        self.logs = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(logsData) as? [(weight: String, reps: String, complete: Bool)] ?? [(weight: String, reps: String, complete: Bool)]()
        
        // Deocding the primary property
        let isPrimaryData = try container.decode(Data.self, forKey: .isPrimary)
        self.isPrimary = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(isPrimaryData) as? Bool ?? false
    }

    //Encoder function implementation
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        
        // Encoding the name property
        let nameData = try NSKeyedArchiver.archivedData(withRootObject: name, requiringSecureCoding: true)
        try container.encode(nameData, forKey: .name)

        // Encoding the muscleGroup property
        let muscleGroupData = try NSKeyedArchiver.archivedData(withRootObject: muscleGroup, requiringSecureCoding: true)
        try container.encode(muscleGroupData, forKey: .muscleGroup)

        // Encoding the description property
        let descriptionData = try NSKeyedArchiver.archivedData(withRootObject: description, requiringSecureCoding: true)
        try container.encode(descriptionData, forKey: .description)
        
        // Encoding the image property
        let imageData = try NSKeyedArchiver.archivedData(withRootObject: image, requiringSecureCoding: true)
        try container.encode(imageData, forKey: .image)
        
        // Encoding the logs property
        let logsData = try NSKeyedArchiver.archivedData(withRootObject: logs, requiringSecureCoding: true)
        try container.encode(logsData, forKey: .logs)
        
        // Encoding the primary property
        let isPrimaryData = try NSKeyedArchiver.archivedData(withRootObject: isPrimary, requiringSecureCoding: true)
        try container.encode(isPrimaryData, forKey: .isPrimary)
     }
}
class Workout: Codable {
    var title: String
    var date: Date
    let duration: String //in minutes
    var exercises = [Exercise]()
    private let isPrimary: Bool
    
    //Init function
    init(title: String, date: Date, duration: String, exercises: [Exercise], isPrimary: Bool = false) {
        self.title = title
        self.date = date
        self.duration = duration
        self.exercises = exercises
        self.isPrimary = isPrimary
    }
    
    // public enum CodingKeys: String, CodingKey {
    //    case title
    //    case date
    //    case duration
    //    case exercises
    //    case isPrimary
    // }

    // //Decoder function implementation
    // required init(from decoder: Decoder) throws {
    //     let container = try decoder.container(keyedBy: CodingKeys.self)

    //     // Decoding the title property
    //     let titleData = try container.decode(Data.self, forKey: .title)
    //     self.title = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(titleData) as? String ?? "New Workout"

    //     // Decoding the date property
    //     let dateData = try container.decode(Data.self, forKey: .date)
    //     self.date = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(dateData) as? Date ?? Date()

    //     // Decoding the duration property
    //     let durationData = try container.decode(Data.self, forKey: .duration)
    //     self.duration = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(durationData) as? String ?? "0"

    //     //Decoding the exercises property
    //     let exercisesData = try container.decode(Data.self, forKey: .exercises)
    //     self.exercises = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(exercisesData) as? [Exercise] ?? [Exercise]()

    //     // Deocding the primary property
    //     let isPrimaryData = try container.decode(Data.self, forKey: .isPrimary)
    //     self.isPrimary = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(isPrimaryData) as? Bool ?? false
    // }

    // //Encoder function implementation
    // func encode(to encoder: Encoder) throws {
    //     var container = encoder.container(keyedBy: CodingKeys.self)

    //     // Encoding the title property
    //     let titleData = try NSKeyedArchiver.archivedData(withRootObject: title, requiringSecureCoding: true)
    //     try container.encode(titleData, forKey: .title)

    //     // Encoding the date property
    //     let dateData = try NSKeyedArchiver.archivedData(withRootObject: date, requiringSecureCoding: true)
    //     try container.encode(dateData, forKey: .date)

    //     // Encoding the duration property
    //     let durationData = try NSKeyedArchiver.archivedData(withRootObject: duration, requiringSecureCoding: true)
    //     try container.encode(durationData, forKey: .duration)

    //     // Encoding the exercises property
    //     let exercisesData = try NSKeyedArchiver.archivedData(withRootObject: exercises, requiringSecureCoding: true)
    //     try container.encode(exercisesData, forKey: .exercises)

    //     // Encoding the primary property
    //     let isPrimaryData = try NSKeyedArchiver.archivedData(withRootObject: isPrimary, requiringSecureCoding: true)
    //     try container.encode(isPrimaryData, forKey: .isPrimary)
    // }
}

答案1

得分: 3

这里是你的代码的翻译部分:

没有任何理由在你的 `encode` 中使用 `NSKeyedArchiver`,也没有理由在你的 `init` 中使用 `NSKeyedUnarchiver`。只需使用`Encoder``Decoder`提供的API

这是经过整理的代码。我用另一个结构体替换了用于日志的元组。我将你的两个类改成了结构体。只有 `Exercise` 结构体需要自定义的 `init`  `encode`,因为 `UIImage` 不符合 `Codable`。自定义 `init` 包含所有属性的唯一需要是支持某些属性的默认值。

请注意,如果一个结构体的所有属性都是 `Codable`,那么整个结构体都是 `Codable`,无需编写任何代码。`Equatable` 同样适用。

struct Log: Codable {
    let weight: String
    let reps: String
    let complete: Bool
}

struct Exercise: Equatable, Codable {
    let name: String
    let muscleGroup: String
    let description: String
    let image: UIImage
    let logs: [Log]
    let isPrimary: Bool

    init(name: String, muscleGroup: String, description: String, image: UIImage, logs: [Log] = [], isPrimary: Bool = false) {
        self.name = name
        self.muscleGroup = muscleGroup
        self.description = description
        self.image = image
        self.logs = logs
        self.isPrimary = isPrimary
    }

    // 覆盖等值比较以检查唯一性
    static func == (lhs: Exercise, rhs: Exercise) -> Bool {
        return lhs.name.lowercased() == rhs.name.lowercased()
    }

    public enum CodingKeys: String, CodingKey {
        case name
        case muscleGroup
        case description
        case image
        case logs
        case isPrimary
    }

    // 解码器函数实现
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        // 解码 name 属性
        name = try container.decode(String.self, forKey: .name)

        // 解码 muscleGroup 属性
        muscleGroup = try container.decode(String.self, forKey: .muscleGroup)

        // 解码 description 属性
        description = try container.decode(String.self, forKey: .description)

        // 解码 image 属性
        let imageData = try container.decode(Data.self, forKey: .image)
        image = UIImage(data: imageData)!

        // 解码 logs 属性
        logs = try container.decode([Log].self, forKey: .logs)

        // 解码 primary 属性
        isPrimary = try container.decode(Bool.self, forKey: .isPrimary)
    }

    // 编码器函数实现
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)

        // 编码 name 属性
        try container.encode(name, forKey: .name)

        // 编码 muscleGroup 属性
        try container.encode(muscleGroup, forKey: .muscleGroup)

        // 编码 description 属性
        try container.encode(description, forKey: .description)

        // 编码 image 属性
        let imageData = image.pngData()
        try container.encode(imageData, forKey: .image)

        // 编码 logs 属性
        try container.encode(logs, forKey: .logs)

        // 编码 primary 属性
        try container.encode(isPrimary, forKey: .isPrimary)
    }
}

struct Workout: Codable {
    let title: String
    let date: Date
    let duration: String //以分钟为单位
    let exercises: [Exercise]
    let isPrimary: Bool

    init(title: String, date: Date, duration: String, exercises: [Exercise], isPrimary: Bool = false) {
        self.title = title
        self.date = date
        self.duration = duration
        self.exercises = exercises
        self.isPrimary = isPrimary
    }
}

希望这有助于你理解和使用你的代码。如果你有任何其他问题,请随时提问。

英文:

There's no reason at all to be using NSKeyedArchiver in your encode and no reason to use NSKeyedUnarchiver in your init. Just use the APIs provided by Encoder and Decoder.

Here's your code all cleaned up. I replaced the tuple being used for the logs with another struct. I changed your two classes to be struct. Only the Exercise struct needs a custom init and encode since UIImage doesn't conform to Codable. The only need for the custom init with all of the properties is to support default values for some of the properties.

Note that if a struct has properties that are all Codable then the whole struct is Codable without writing any code. Same for Equatable.

struct Log: Codable {
let weight: String
let reps: String
let complete: Bool
}
struct Exercise: Equatable, Codable {
let name: String
let muscleGroup: String
let description: String
let image: UIImage
let logs: [Log]
let isPrimary: Bool
init(name: String, muscleGroup: String, description: String, image: UIImage, logs: [Log] = [], isPrimary: Bool = false) {
self.name = name
self.muscleGroup = muscleGroup
self.description = description
self.image = image
self.logs = logs
self.isPrimary = isPrimary
}
//Override equatable to check for uniqueness
static func == (lhs: Exercise, rhs: Exercise) -> Bool {
return lhs.name.lowercased() == rhs.name.lowercased()
}
public enum CodingKeys: String, CodingKey {
case name
case muscleGroup
case description
case image
case logs
case isPrimary
}
//Decoder function implementation
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// Decoding the name property
name = try container.decode(String.self, forKey: .name)
// Decoding the muscleGroup property
muscleGroup = try container.decode(String.self, forKey: .muscleGroup)
// Decoding the description property
description = try container.decode(String.self, forKey: .description)
// Decoding the image property
let imageData = try container.decode(Data.self, forKey: .image)
image = UIImage(data: imageData)!
// Deocding the logs property
logs = try container.decode([Log].self, forKey: .logs)
// Deocding the primary property
isPrimary = try container.decode(Bool.self, forKey: .isPrimary)
}
//Encoder function implementation
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
// Encoding the name property
try container.encode(name, forKey: .name)
// Encoding the muscleGroup property
try container.encode(muscleGroup, forKey: .muscleGroup)
// Encoding the description property
try container.encode(description, forKey: .description)
// Encoding the image property
let imageData = image.pngData()
try container.encode(imageData, forKey: .image)
// Encoding the logs property
try container.encode(logs, forKey: .logs)
// Encoding the primary property
try container.encode(isPrimary, forKey: .isPrimary)
}
}
struct Workout: Codable {
let title: String
let date: Date
let duration: String //in minutes
let exercises: [Exercise]
let isPrimary: Bool
init(title: String, date: Date, duration: String, exercises: [Exercise], isPrimary: Bool = false) {
self.title = title
self.date = date
self.duration = duration
self.exercises = exercises
self.isPrimary = isPrimary
}
}

huangapple
  • 本文由 发表于 2023年4月20日 06:05:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/76059142.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定