英文:
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
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论