【问题标题】:How to conform an ObservableObject to the Codable protocols?如何使 ObservableObject 符合 Codable 协议?
【发布时间】:2023-03-06 20:32:01
【问题描述】:

在 SwiftUI beta 5 中,Apple 引入了 @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
        }
    }
}

【问题讨论】:

  • 您是否尝试过自己调用 objectWillChange.send() ?如果您不想这样做,那么使用自定义 init(from:) 应该可以解决问题。

标签: swift swiftui codable


【解决方案1】:

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)
}

【讨论】:

  • 我在我的一个屏幕@ObservedObject var meal = Meal() 中有这行代码,现在会产生以下错误:“调用中的参数'from' 缺少参数”。你知道我应该传递给构造函数的参数吗?
  • 如果你要直接创建一个 Meal 对象(而不是通过 json),那么你需要创建一个初始化器。 init() { }
  • 注意你的类需要定义 CodingKeys。 (提问者的代码确实定义了 CodingKeys,但如果您将其应用于您自己的课程,请注意这一点)
【解决方案2】:

经过多次修改后,我设法将 Codable 直接添加到 @Published

注意我必须为 iOS14 更新这个。这说明了挖掘未记录类型的危险......

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

更多信息在这里 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)")
        }
    }
}

【讨论】:

  • 嘿!不错的答案。您是否准备好任何单元测试来检查我的?
  • 谢谢,但我最初的实现通过了,使用剥离,但不是在实际应用程序中,这需要挖掘私有 Publisher 和 Subject 类型,如您所示。我需要一个需要这些东西的可复制的!
  • 在你完善的时候分享
  • 我在 Xcode 12 中收到错误 'Any' 类型的值没有成员 'value''
【解决方案3】:

困惑的沃隆有正确的想法!我正在尝试与他们合作以使某些东西变得强大。我相信这应该是你所需要的,从 Xcode 12 构建。

PublishedstoragePublished.Publisher's subject 是私有 API,因此镜像是在需要的地方挖掘的最佳选择:

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)
    )
  }
}
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
      )
    )
  }
}

【讨论】:

【解决方案4】:

没有Mirror的更高效的变体

已发布+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
    }
}

已发布+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)
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-11-09
    • 2018-07-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多