如何使ObservableObject符合Codable协议?

时间:2019-08-10 16:58:02

标签: swift swiftui codable

在SwiftUI beta 5中,苹果引入了@Published注释。该注释当前阻止此类符合Codable协议。

我如何遵守这些协议,以便可以将此类编码和解码为JSON?您现在可以忽略image属性。

class Meal: ObservableObject, Identifiable, Codable {

    enum CodingKeys: String, CodingKey {
        case id
        case name
        case ingredients
        case numberOfPeople
    }

    var id = Globals.generateRandomId()
    @Published var name: String = "" { didSet { isInputValid() } }
    @Published var image = Image("addImage")
    @Published var ingredients: [Ingredient] = [] { didSet { isInputValid() } }
    @Published var numberOfPeople: Int = 2
    @Published var validInput = false

    func isInputValid() {
        if name != "" && ingredients.count > 0 {
            validInput = true
        }
    }
}

3 个答案:

答案 0 :(得分:8)

经过多次黑客攻击,我设法将Codable直接添加到@Published

请注意,我必须针对iOS14对此进行更新。这说明了挖掘未记录类型的危险...

只需将以下代码添加到文件中,您的@Published变量将自动编码(前提是它们基于Codable类型)

更多信息在这里 https://blog.hobbyistsoftware.com/2020/01/adding-codeable-to-published/

此处的代码:

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 {

    private var valueChild:Any? {
        let mirror = Mirror(reflecting: self)
        if let valueChild = mirror.descendant("value") {
            return valueChild
        }
        
        //iOS 14 does things differently...
        if let valueChild = mirror.descendant("storage","value") {
            return valueChild
        }
        
        //iOS 14 does this too...
        if let valueChild = mirror.descendant("storage","publisher","subject","currentValue") {
            return valueChild
        }

        return nil
    }
   
    public func encode(to encoder: Encoder) throws {
        
        guard let valueChild = valueChild else {
            fatalError("Mirror Mirror on the wall - why no value y'all : \(self)")
        }
        
        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)")
        }
    }
}

答案 1 :(得分:5)

init()encode()方法添加到您的课程中:

required init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)

    id = try values.decode(Int.self, forKey: .id)
    name = try values.decode(String.self, forKey: .name)
    ingredients = try values.decode([Ingredient].self, forKey: .ingredients)
    numberOfPeople = try values.decode(Int.self, forKey: .numberOfPeople)
}

func encode(to encoder: Encoder) throws {
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(id, forKey: .id)
    try container.encode(name, forKey: .name)
    try container.encode(ingredients, forKey: .ingredients)
    try container.encode(numberOfPeople, forKey: .numberOfPeople)
}

答案 2 :(得分:1)

困惑的Vorlon有正确的主意!我正在尝试与他们合作,使它们变得更强大。我相信这应该是您所需要的,都是从Xcode 12构建的。

Published的{​​{1}}和storage Published.Publisher's是私有API,因此镜像是在需要的地方进行挖掘的最佳选择:

subject
import struct Combine.Published

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 Published: Decodable where Value: Decodable {
  public init(from decoder: Decoder) throws {
    self.init(
      initialValue: try .init(from: decoder)
    )
  }
}