If I'm not mistaken, when you have a Codable and RawRepresentable type, the rawValue is used to encode/decode the value. If that's the case, you're incurring in an endless recursion by encoding/decoding in the rawValue definition.
I recently came across this problem when trying to store a codable into UserDefaults through the AppStorage property wrapper. I thought I'd be clever and use RawRepresentable with a String rawValue type but then this recursion came up.
I didn't like the idea of error-prone manual encoding/decoding and so came up with the following solution:
struct CodableWrapper<Value: Codable> {
var value: Value
}
extension CodableWrapper: RawRepresentable {
typealias RawValue = String
var rawValue: RawValue {
guard
let data = try? JSONEncoder().encode(value),
let string = String(data: data, encoding: .utf8)
else {
// TODO: Track programmer error
return ""
}
return string
}
init?(rawValue: RawValue) {
guard
let data = rawValue.data(using: .utf8),
let decoded = try? JSONDecoder().decode(Value.self, from: data)
else {
// TODO: Track programmer error
return nil
}
value = decoded
}
}
These are the best practices I've found to accomplish this goal, better than 90% of the similar responses out there, and avoiding custom code per Codable. Thanks to all who responded for providing these insights.
I've summed it all up into a gist, for others who struggled to understand how to put the parts together.
That said, I encountered a weird problem with this method. My Codable model has several members affected by bindings within a single UI. When applying the techniques from this thread, one of those bindings always fetches a stale value of the model. The problem disappears by simply switching the model back to a @State var!
For now, I'm left with this hideous workaround:
.onChange(of: config) { updated in
// Kludge for stale binding values when using @AppStorage directly.
persistedConfig = updated
}