Skip to content

Instantly share code, notes, and snippets.

@ericlewis
Last active March 3, 2022 14:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ericlewis/e7c7aa2b9d6d62490c718f52f08579b5 to your computer and use it in GitHub Desktop.
Save ericlewis/e7c7aa2b9d6d62490c718f52f08579b5 to your computer and use it in GitHub Desktop.
extension Published: Encodable where Value: Encodable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
let value = _PublishedValueExtractor.shared.extractValue(from: self)
try container.encode(value)
}
}
fileprivate struct _PublishedValueExtractor {
static let shared = _PublishedValueExtractor()
func extractValue<Value>(from published: Published<Value>) -> 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
}
}
@ericlewis
Copy link
Author

Decodable:

extension Published: Decodable where Value: Decodable {
  public init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()
    self = try .init(initialValue: container.decode(Value.self))
  }
}

@ericlewis
Copy link
Author

Bonus ObservedObject:

extension ObservedObject: Codable where ObjectType: Codable {
  public init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()
    self = try .init(initialValue: container.decode(ObjectType.self))
  }

  public func encode(to encoder: Encoder) throws {
    var container = encoder.singleValueContainer()
    try container.encode(self)
  }
}

@ericlewis
Copy link
Author

The rest of our friends:

extension StateObject: Codable where ObjectType: Codable {
  public init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()
    let value = try container.decode(ObjectType.self)
    self = .init(wrappedValue: value)
  }

  public func encode(to encoder: Encoder) throws {
    var container = encoder.singleValueContainer()
    try container.encode(self)
  }
}

extension State: Codable where Value: Codable {
  public init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()
    self = try .init(initialValue: container.decode(Value.self))
  }

  public func encode(to encoder: Encoder) throws {
    var container = encoder.singleValueContainer()
    try container.encode(self)
  }
}

@ericlewis
Copy link
Author

ericlewis commented Mar 3, 2022

Scene & AppStorage example:

// Note: we have to make MANY overloads
extension AppStorage: Decodable where Value == Bool {
  public init(from decoder: Decoder) throws where Value == Bool {
    let container = try decoder.singleValueContainer()
    let result = try container.decode(_StorageContainer<Value>.self)
    self = .init(wrappedValue: result.value, result.key)
  }
}

extension AppStorage: Encodable where Value: Codable {
  public func encode(to encoder: Encoder) throws {
    var container = encoder.singleValueContainer()
    guard let key = Mirror(reflecting: self).descendant(0, 0) as? String else {
      throw EncodingError.invalidValue(self, .init(codingPath: [], debugDescription: ""))
    }
    try container.encode(_StorageContainer(key: key, value: self.wrappedValue))
  }
}

extension SceneStorage: Decodable where Value == Bool {
  public init(from decoder: Decoder) throws where Value == Bool {
    let container = try decoder.singleValueContainer()
    let result = try container.decode(_StorageContainer<Value>.self)
    self = .init(wrappedValue: result.value, result.key)
  }
}

extension SceneStorage: Encodable where Value: Codable {
  public func encode(to encoder: Encoder) throws {
    var container = encoder.singleValueContainer()
    guard let key = Mirror(reflecting: self).descendant(0, 0) as? String else {
      throw EncodingError.invalidValue(self, .init(codingPath: [], debugDescription: "Could not extract key."))
    }
    try container.encode(_StorageContainer(key: key, value: self.wrappedValue))
  }
}

fileprivate struct _StorageContainer<Value: Codable>: Codable {
  let key: String
  let value: Value
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment