如何使 UIImage 符合 Codable?

Posted

技术标签:

【中文标题】如何使 UIImage 符合 Codable?【英文标题】:How to conform UIImage to Codable? 【发布时间】:2018-02-22 04:18:31 【问题描述】:

Swift 4 有 Codable,它很棒。但是UIImage默认不符合。我们该怎么做?

我试过singleValueContainerunkeyedContainer

extension UIImage: Codable 
  // 'required' initializer must be declared directly in class 'UIImage' (not in an extension)
  public required init(from decoder: Decoder) throws 
    let container = try decoder.singleValueContainer()
    let data = try container.decode(Data.self)
    guard let image = UIImage(data: data) else 
      throw MyError.decodingFailed
    

    // A non-failable initializer cannot delegate to failable initializer 'init(data:)' written with 'init?'
    self.init(data: data)
  

  public func encode(to encoder: Encoder) throws 
    var container = encoder.singleValueContainer()
    guard let data = UIImagePNGRepresentation(self) else 
      return
    

    try container.encode(data)
  

我收到 2 个错误

    “必需”初始化程序必须直接在“UIImage”类中声明(而不是在扩展中) 不可失败的初始化程序不能委托给用“init?”编写的可失败初始化程序“init(data:)”

一种解决方法是使用包装器。但是还有其他方法吗?

【问题讨论】:

您究竟为什么要将UIImageCodable 一致?图像通常不适合编码为 JSON 或 XML 等格式。通常最好单独编码图像,然后在 JSON 中编码例如 URL。 如果您需要将图像保存在 JSON 字符串中,只需将图像数据转换为 base64 字符串并将其保存为字符串 @Hamish @LeoDabus 我没有在我的问题中提到 json 或 xml。我想你建议JSONEncoder?但这只是Encoder协议的一种实现 @onmyway133 我的主要问题只是问为什么你想要这个:) 其余的是基于@现在提供的当前(和常用)编码器/解码器的假设987654333@. 【参考方案1】:

解决方案:滚动您自己的符合 Codable 的包装类。

一个解决方案,因为对UIImage 的扩展已经结束,所以将图像包装在您拥有的新类中。否则,您的尝试基本上是直接的。我看到这在 Hyper Interactive 的缓存框架中完美地完成了,称为 Cache。

虽然您需要访问该库以深入了解依赖项,但您可以通过查看他们的 ImageWrapper 类来了解这个想法,该类是为这样使用而构建的:

let wrapper = ImageWrapper(image: starIconImage)
try? theCache.setObject(wrapper, forKey: "star")

let iconWrapper = try? theCache.object(ofType: ImageWrapper.self, forKey: "star")
let icon = iconWrapper.image

这是他们的包装类:

// Swift 4.0
public struct ImageWrapper: Codable 
  public let image: Image

  public enum CodingKeys: String, CodingKey 
    case image
  

  // Image is a standard UI/NSImage conditional typealias
  public init(image: Image) 
    self.image = image
  

  public init(from decoder: Decoder) throws 
    let container = try decoder.container(keyedBy: CodingKeys.self)
    let data = try container.decode(Data.self, forKey: CodingKeys.image)
    guard let image = Image(data: data) else 
      throw StorageError.decodingFailed
    

    self.image = image
  

  // cache_toData() wraps UIImagePNG/JPEGRepresentation around some conditional logic with some whipped cream and sprinkles.
  public func encode(to encoder: Encoder) throws 
    var container = encoder.container(keyedBy: CodingKeys.self)
    guard let data = image.cache_toData() else 
        throw StorageError.encodingFailed
    

    try container.encode(data, forKey: CodingKeys.image)
  

我很想听听你最终使用的是什么。

更新:事实证明OP 编写了我引用的代码(Swift 4.0 更新到缓存)来解决问题。当然,代码应该放在这里,但我也会不加编辑,因为这一切都具有戏剧性的讽刺意味。 :)

【讨论】:

谢谢。你知道我实现了?请查看提交 啊,我现在当然知道了! ? 没想到世界这么小。学过的知识。嗯,这是否也意味着“我的”答案是已接受的答案,嗯? 嗨,我想看看有没有比我更聪明的解决方案 FWIW,我可能会建议抛出 DecodingError.dataCorruptedError(forKey:in:debugDescription:) 等。 感谢您介绍这个包装器,工作就像一个魅力。我想看看编码器/解码器的容器在这种情况下是如何工作的。【参考方案2】:

最简单的方法是直接使用Data 而不是UIImage

public struct SomeImage: Codable 

    public let photo: Data
    
    public init(photo: UIImage) 
        self.photo = photo.pngData()!
    

反序列化图像:

UIImage(data: instanceOfSomeImage.photo)!

【讨论】:

【参考方案3】:

您可以使用非常优雅的解决方案,使用 KeyedDecodingContainerKeyedEncodingContainer 类的扩展:

enum ImageEncodingQuality 
  case png
  case jpeg(quality: CGFloat)


extension KeyedEncodingContainer 
  mutating func encode(
    _ value: UIImage,
    forKey key: KeyedEncodingContainer.Key,
    quality: ImageEncodingQuality = .png
  ) throws 
    let imageData: Data?
    switch quality 
    case .png:
      imageData = value.pngData()
    case .jpeg(let quality):
      imageData = value.jpegData(compressionQuality: quality)
    
    guard let data = imageData else 
      throw EncodingError.invalidValue(
        value,
        EncodingError.Context(codingPath: [key], debugDescription: "Failed convert UIImage to data")
      )
    
    try encode(data, forKey: key)
  


extension KeyedDecodingContainer 
  func decode(
    _ type: UIImage.Type,
    forKey key: KeyedDecodingContainer.Key
  ) throws -> UIImage 
    let imageData = try decode(Data.self, forKey: key)
    if let image = UIImage(data: imageData) 
      return image
     else 
      throw DecodingError.dataCorrupted(
        DecodingError.Context(codingPath: [key], debugDescription: "Failed load UIImage from decoded data")
      )
    
  

PS:您可以使用这种方式将Codable 用于任何类类型

【讨论】:

这是一个很好的答案! +1 但是,我会将 ImageEncodingQuality 枚举更改为 enum ImageType case png; case jpeg(CGFloat) @freytag 你在读我的想法,我已经在我的项目中切换到ImageType 的自定义枚举:-) 相当不错!但你应该使用DecodingErrorEncodingError【参考方案4】:

传递 UIImage 的一种方法是将其转换为符合 Codable 的内容,例如 String。

将 UIImage 转换为 func encode(to encoder: Encoder) throws 内的字符串:

let imageData: Data = UIImagePNGRepresentation(image)!
let strBase64 = imageData.base64EncodedString(options: .lineLength64Characters)
try container.encode(strBase64, forKey: .image)

将String转换回required init(from decoder: Decoder) throws内的UIImage:

let strBase64: String = try values.decode(String.self, forKey: .image)
let dataDecoded: Data = Data(base64Encoded: strBase64, options: .ignoreUnknownCharacters)!
image = UIImage(data: dataDecoded)

【讨论】:

Data 实现了Codable,所以你不需要转换成字符串;)developer.apple.com/documentation/foundation/data/…【参考方案5】:

现有的答案似乎都是不正确的。如果您将反序列化的图像与原始图像进行比较,您会发现它们在任何意义上都可能不相等。这是因为答案都在扔掉 scale 信息。

您必须对图像scale 及其pngData() 进行编码。然后在解码 UIImage 时,通过调用init(data:scale:) 将数据与比例结合起来。

【讨论】:

请参阅***.com/a/68137443/341994,了解通过相等测试的可编码图像包装器的实际示例。【参考方案6】:

还有一个在图像上使用惰性变量的简单解决方案:

var mainImageData: Data 
    didSet  _ = mainImage 

lazy var mainImage: UIImage = 
    UIImage(data: mainImageData)!
()

这样,在对象初始化和分配给mainImageData 期间,它的didSet 将启动,然后将启动UIImage 的初始化。

由于UIImage 初始化占用大量资源,我们将它们耦合在一起。 请注意,整个初始化将在后台线程上进行。

【讨论】:

以上是关于如何使 UIImage 符合 Codable?的主要内容,如果未能解决你的问题,请参考以下文章

如何在目标 c 数据模型类中使用 Codable 协议?

如何在 Codable 中使用 List 类型? (RealmSwift)

Codable : 不符合协议'Decodable'

如果某些东西符合 Codable ,它会永远无法被编码或解码吗?

Codable 类不符合协议 Decodable

MultipeerConnectivity MCPeerID 不符合 Codable