37

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 个错误

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

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

4

6 回答 6

31

最简单的方法是直接制作它Data而不是UIImage

public struct SomeImage: Codable {

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

反序列化图像:

UIImage(data: instanceOfSomeImage.photo)!
于 2019-03-17T19:27:25.457 回答
31

一个解决方案:滚动你自己的符合 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 更新到缓存)来解决问题。当然,代码应该放在这里,但由于这一切具有戏剧性的讽刺意味,我也会不加编辑的话。:)

于 2017-09-20T23:27:53.467 回答
19

您可以使用扩展名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任何类类型

于 2018-06-28T18:21:32.757 回答
7

传递 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)

要将字符串转换回 UIImage 内部required init(from decoder: Decoder) throws

let strBase64: String = try values.decode(String.self, forKey: .image)
let dataDecoded: Data = Data(base64Encoded: strBase64, options: .ignoreUnknownCharacters)!
image = UIImage(data: dataDecoded)
于 2018-03-01T08:12:46.580 回答
3

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

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

于 2021-06-27T21:42:29.950 回答
0

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

var mainImageData: Data {
    didSet { _ = mainImage }
}
lazy var mainImage: UIImage = {
    UIImage(data: mainImageData)!
}()

这样,在对象初始化和分配给的过程中mainImageData,它didSet会启动,然后启动UIImage.

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

于 2021-05-05T07:46:28.170 回答