Adding Codeable to @Published

Codable is a kind of magic.

@Published certainly behaves like magic.

But if you try to add a @Published property to your codeable, then XCode complains

This one had me frustrated for many hours. The issue is that the encoder gets an instance of Published<Type> to encode. As far as I know there isn’t a proper way to get the actual value.
(normally, your code will call the getter on wrappedValue, but I’m not aware of a way to call that directly with Published)

In the end, the only solution I found was to Mirror the struct and extract the value from the ‘value’ Mirror.Child

This has been pretty typical of my SwiftUI experience. There is lots of magical functionality that falls out for minimal effort in a magical way.

But then you try to push the edges, act like a programmer and extend things – and you find that the basic API seems designed to frustrate you. Why would the Published struct not have a simple way to access the wrapped variable?

Anyway, for now at least – we can hack around the limitations

import Foundation
import SwiftUI

extension Published:Encodable where Value:Decodable {

    public func encode(to encoder: Encoder) throws {

        let mirror = Mirror(reflecting: self)
        if let valueChild = mirror.children.first(where: { (child) -> Bool in
            child.label == "value"
        }) {
            if let value = valueChild.value as? Encodable {
                do {
                    try value.encode(to: encoder)
                    return
                } catch let error {
                    assertionFailure("Failed encoding: \(self) - \(error)")
                }
            }
            else {
                assertionFailure("Decodable Value not decodable. Odd \(self)")
            }
        }
        else {
            assertionFailure("Mirror Mirror on the wall - why no value y'all : \(self)")
        }
    }
}

extension Published:Decodable where Value:Decodable {
    public init(from decoder: Decoder) throws {
        self = Published(initialValue:try Value(from:decoder))
    }
}