1

My codable observable Thing compiles:

class Thing: Codable, ObservableObject {
    var feature: String
}

Wrapping feature in @Published though doesn’t:

class Thing: Codable, ObservableObject {
    @Published var feature: String
}
 Class 'Thing' has no initializers
 Type 'Thing' does not conform to protocol 'Decodable'
 Type 'Thing' does not conform to protocol 'Encodable'

Apparently Codable conformance can’t be synthesized anymore because @Published doesn’t know how to encode/decode its wrappedValue (because it doesn’t conform to Codable even if its wrapped value does)?

Okay, so I’ll let it know how to do it!

extension Published: Codable where Value: Codable {
    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(wrappedValue) //  'wrappedValue' is unavailable: @Published is only available on properties of classes
    }

    public init(from decoder: Decoder) throws {
        var container = try decoder.singleValueContainer()
        wrappedValue = try container.decode(Value.self) //  'wrappedValue' is unavailable: @Published is only available on properties of classes
    }
}

So sorrow, much misery, many sadness!

How can I easily add back Codable synthesis (or similar) without defining encode(to:) and init(from:)?

  • 1
    I think synthesizing `Codable` through a property wrapper like `@Published` is probably a recipe for more headaches than you realize. Imagine the general case where other `Codable` things reference the same instance of `Thing` and are then encoded. Each one would encode `Thing` as though it were its own value. But then when you decode them, each one would create its own instance. That will almost certainly break things. Though actually that's more of a general problem encoding reference types anyway. – Chip Jarred Jan 04 '23 at 07:55
  • It's highly recommended to separate the ***data** model* from the ***view** model*. – vadian Jan 04 '23 at 08:55
  • @vadian Can you elaborate? Do you consider `@Published` a view property wrapper? – Hugo Timothy Jan 04 '23 at 09:06
  • Compared to `UIKit` an `@ObservableObject` *view model* is a `ViewController` and you would never adopt Codable in a view controller. – vadian Jan 04 '23 at 09:13
  • Interesting perspective. Yet I would not agree with this comparison. – Hugo Timothy Jan 04 '23 at 09:59
  • I would never want to make the complex machinery of a Finite State Automaton codable, when it would be just sufficient to remember the `State`, and then make State codable, and then use this value to set the initial state of the FSA and let it continue to run where it stopped after reboot. ;) Hope you get the idea. ;) – CouchDeveloper Jan 04 '23 at 11:52
  • [Apple does say that `ObservableObject`s can be used for Model data](https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app), after all CoreData objects are `ObservableObject`s, but a `struct` is a simpler solution for many instances, there is no way around having to create your own custom conformance methods. – lorem ipsum Jan 04 '23 at 14:56
  • The accepted answer does not answer your question but the others do: [How to conform an ObservableObject to the Codable protocols?](https://stackoverflow.com/questions/57444059/how-to-conform-an-observableobject-to-the-codable-protocols) –  Jan 04 '23 at 16:27

1 Answers1

-1

I don't know why you would want to do it, I think you should code the struct, not the published class. Something like this:

struct CodableStruct: Codable {
    var feature1: String = ""
    var feature2: Int = 0
}

class Thing: ObservableObject {
    @Published var features: CodableStruct = .init()
}
Simone Pistecchia
  • 2,746
  • 3
  • 18
  • 30