-
-
Save ConfusedVorlon/276bd7ac6c41a99ea0514a34ee9afc3d to your computer and use it in GitHub Desktop.
import Foundation | |
import SwiftUI | |
extension Published:Decodable where Value:Decodable { | |
public init(from decoder: Decoder) throws { | |
let decoded = try Value(from:decoder) | |
self = Published(initialValue:decoded) | |
} | |
} | |
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: { $0.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)") | |
} | |
} | |
} |
Just in case this is still relevant, I stumbled upon this variant, which works with the latest swift version. This is not from me, but from here: https://stackoverflow.com/questions/57444059/how-to-conform-an-observableobject-to-the-codable-protocols
extension Published: Decodable where Value: Decodable
{
public init(from decoder: Decoder) throws
{
self.init(initialValue: try .init(from: decoder))
}
}
extension Published: Encodable where Value: Encodable
{
public func encode(to encoder: Encoder) throws {
guard
let storageValue =
Mirror(reflecting: self).descendant("storage")
.map(Mirror.init)?.children.first?.value,
let value =
storageValue as? Value
??
(storageValue as? Publisher).map(Mirror.init)?
.descendant("subject", "currentValue")
as? Value
else { throw EncodingError.invalidValue(self, codingPath: encoder.codingPath) }
try value.encode(to: encoder)
}
}
extension EncodingError
{
/// `invalidValue` without having to pass a `Context` as an argument.
static func invalidValue(_ value: Any, codingPath: [CodingKey], debugDescription: String = .init()) -> Self
{
.invalidValue(value, .init(codingPath: codingPath, debugDescription: debugDescription) )
}
}
A more efficient variant without Mirror
Published+Value.swift
private class PublishedWrapper<T> {
@Published private(set) var value: T
init(_ value: Published<T>) {
_value = value
}
}
extension Published {
var unofficialValue: Value {
PublishedWrapper(self).value
}
}
Published+Codable.swift
extension Published: Decodable where Value: Decodable {
public init(from decoder: Decoder) throws {
self.init(wrappedValue: try .init(from: decoder))
}
}
extension Published: Encodable where Value: Encodable {
public func encode(to encoder: Encoder) throws {
try unofficialValue.encode(to: encoder)
}
}
That's brilliantly simple - thank you.
I do find the language unsatisfactory in this behaviour.
It seems wrong that accessing an identical variable by putting it inside another class should change its type for the code-synthesizer...
@Stevenmagdy , you are amazing! Your code worked perfectly! (The existing code kept crashing). Thank you!
I tried @StevenSorial 's solution, however it doesn't work if the type of a @Published
variable is any
protocol.
e.g.:
class Parent {
@Published var child: any Child
}
protocol Child: Codable { }
class CustomChild: Child {
var value: String
}
The error is:
Cannot automatically synthesize 'Decodable' because 'Published <any Child>' does not conform to 'Decodable'
Do you think this is possible?
The motivation for using a protocol instead of a superclass is because using a superclass requires all of the subclasses to implement the encoders and decoders.
An approach that is working instead of protocols is using enums:
class Parent {
@Published var child: Child
}
enum Child: Codable {
case custom(_ child: CustomChild)
}
class CustomChild: Codable {
var value: String
}
In Swift 5.3, self cannot be reached.
valueChild
becomes nil. Do you have an idea about why this happens?