如何使 ObservableObject 符合 Codable 协议?
How to conform an ObservableObject to the Codable protocols?
在 SwiftUI beta 5 中,Apple 引入了@Published 注解。此注解当前阻止此 class 符合 Codable 协议。
我如何才能符合这些协议,以便我可以将此 class 编码和解码为 JSON?您现在可以忽略图片 属性。
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
}
}
}
将 init()
和 encode()
方法添加到您的 class:
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)
}
经过多次修改,我设法将 Codable 直接添加到 @Published
请注意,我必须为 iOS14 更新此内容。这说明了在未记录的类型中挖掘的危险...
只需将下面的代码添加到文件中,您的@Published 变量将自动成为 Codable(前提是它们基于 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)")
}
}
}
糊涂的沃隆有理!我正试图与他们合作,使之变得更强大。我相信这应该是您所需要的,从 Xcode 12.
构建
Published
的 storage
和 Published.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
)
)
}
}
没有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)
}
}
在 SwiftUI beta 5 中,Apple 引入了@Published 注解。此注解当前阻止此 class 符合 Codable 协议。
我如何才能符合这些协议,以便我可以将此 class 编码和解码为 JSON?您现在可以忽略图片 属性。
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
}
}
}
将 init()
和 encode()
方法添加到您的 class:
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)
}
经过多次修改,我设法将 Codable 直接添加到 @Published
请注意,我必须为 iOS14 更新此内容。这说明了在未记录的类型中挖掘的危险...
只需将下面的代码添加到文件中,您的@Published 变量将自动成为 Codable(前提是它们基于 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)")
}
}
}
糊涂的沃隆有理!我正试图与他们合作,使之变得更强大。我相信这应该是您所需要的,从 Xcode 12.
构建Published
的 storage
和 Published.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
)
)
}
}
没有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)
}
}