📃

@Published を Codable にする

2022/09/05に公開1

CodingKeys 書くのめんどくさいよね。

Published+Decodable

extension Published: Decodable where Value: Decodable {

    public init(from decoder: Decoder) throws {
        self.init(wrappedValue: try Value(from: decoder))
    }
}

Published+Encodable

extension Published: Encodable where Value: Encodable {

    public func encode(to encoder: Encoder) throws {
        try Published.extract(from: self).encode(to: encoder)
    }

    private static func extract(from published: Self) -> Value {
        var published = published
        let semaphore = DispatchSemaphore(value: 0)
        var value: Value!
        let _ = published.projectedValue.sink {
            defer { semaphore.signal() }
            value = $0
        }
        semaphore.wait()
        return value
    }
}

Playground

import Foundation

class Sample: ObservableObject, Codable {
    @Published var name: String
    init(name: String) {
        self.name = name
    }
}

extension Published: Decodable where Value: Decodable {

    public init(from decoder: Decoder) throws {
        self.init(wrappedValue: try Value(from: decoder))
    }
}

extension Published: Encodable where Value: Encodable {

    public func encode(to encoder: Encoder) throws {
        try Published.extract(from: self).encode(to: encoder)
    }

    private static func extract(from published: Self) -> Value {
        var published = published
        let semaphore = DispatchSemaphore(value: 0)
        var value: Value!
        let _ = published.projectedValue.sink {
            defer { semaphore.signal() }
            value = $0
        }
        semaphore.wait()
        return value
    }
}

let o = Sample(name: "Nia")
let s = try JSONEncoder().encode(o)
print(String(data: s, encoding: .utf8)!)
let r = try JSONDecoder().decode(Sample.self, from: s)
print(r.name)

let _ = o.$name.sink {
    print($0)
}

参考

https://blog.hobbyistsoftware.com/2020/01/adding-codeable-to-published/

https://gist.github.com/ericlewis/e7c7aa2b9d6d62490c718f52f08579b5

Discussion

NiaNia

この方法には意味不明なバグがある。Date型をパースしようとすると、JSONDecoderのdateDecodingStrategyが適用されない。まあおそらく Value(from:) の時点で Date 型の型情報が欠落するんだろう。