编码/解码时 UIImage 不等效 [英] UIImage not equivalent when encoding/decoding

查看:43
本文介绍了编码/解码时 UIImage 不等效的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在对我的模型进行一些测试,以确保当我将它们编码为 JSON 然后使用 JSONEncoder/Decoder 将它们解码回来时它们是相等的.但是,我的一项测试失败了,罪魁祸首是 UIImage.我确保在编码/解码过程中没有抛出任何错误.

I've been doing some tests on my models to make sure they are equal when I encode them into JSON and then decode them back using JSONEncoder/Decoder. However, one of my tests failed, and the culprit was UIImage. I've made sure that no errors were thrown during the encoding/decoding process.

首先,这是有问题的测试:

First of all, this is the test in question:

func testProfileImageCodable() throws {
    let image = ProfileImage(UIImage(systemName: "applelogo")!)
    try XCTAssertTrue(assertCodable(image))
}

这是我的可编码性"测试,我确保编码/解码前后的类型相同:

Here's my "Codability" test, where I make sure that types are equal before and after encoding/decoding:

func assertCodable<T: Codable & Equatable>(
    _ value: T,
    decoder: JSONDecoder = .init(),
    encoder: JSONEncoder = .init()
) throws -> Bool {
    let encoded = try encoder.encode(value)
    let decoded = try decoder.decode(T.self, from: encoded)
    
    return value == decoded
}

首先,这是我如何使 UIImageCodable 一起工作:

Firstly, here's how I made UIImage work with Codable:

extension KeyedEncodingContainer {
    mutating func encode(_ value: UIImage, forKey key: Key) throws {
        guard let data = value.pngData() 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: 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")
            )
        }
    }
}

UIImage 存在于 ProfileImage 类型中,因此使其符合 Codable 看起来像这样:

The UIImage lives in a ProfileImage type, so conforming it to Codable looks like this:

extension ProfileImage: Codable {
    enum CodingKeys: CodingKey {
        case image
    }
    
    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.image = try container.decode(UIImage.self, forKey: .image)
    }
    
    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(self.image, forKey: .image)
    }
}

此外,ProfileImageEquatable 一致性使用 isEqual(_:)UIImage 属性上,他们说是确定两个图像是否包含相同图像数据的唯一可靠方法."

Furthermore, ProfileImage's Equatable conformance uses isEqual(_:) on the UIImage property, which they say is "the only reliable way to determine whether two images contain the same image data."

然而,我的测试仍然失败,我不知道为什么.任何帮助将不胜感激.

Yet, my test still fails, and I'm not sure why. Any help would be greatly appreciated.

推荐答案

判断两张图片是否包含相同图片数据的唯一可靠方法

the only reliable way to determine whether two images contain the same image data

他们错了.过去那篇文档也误导了我!

They are wrong about that. That piece of the docs has misled me in the past too!

比较两个图像内容(底层位图)是否相等的方法是比较它们的pngData.

The way to compare two images for equality of content (the underlying bitmap) is to compare their pngData.

然而,在最深层次上,您的代码有什么问题,就是 UIImage 具有您丢弃的 scale 信息.例如,您原始图像的 scale 可能是 2 或 3.但是当您在解码时调用 image(data:) 时,您没有考虑到这一点.如果您确实考虑到了这一点,那么您的断言将如您所愿.

What's wrong with your code, however, at the deepest level, is that a UIImage has scale information which you are throwing away. For example, your original image's scale is probably 2 or 3. But when you call image(data:) on decoding, you fail to take that into account. If you did take it into account, your assertion would work as you expect.

我像这样调整了你的代码(可能有更好的方法,我只是想证明 scale 是问题所在):

I tweaked your code like this (there might be a better way, I just wanted to prove that scale was the issue):

struct Image: Codable {
    let image:UIImage
    init(image:UIImage) {
        self.image = image
    }
    enum CodingKeys: CodingKey {
        case image
        case scale
    }
    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let scale = try container.decode(CGFloat.self, forKey: .scale)
        let image = try container.decode(UIImage.self, forKey: .image)
        self.image = UIImage(data:image.pngData()!, scale:scale)!
    }
    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(self.image, forKey: .image)
        try container.encode(self.image.scale, forKey: .scale)
    }
}

这是我的测试:

let im = UIImage(systemName:"applelogo")!
let encoded = try! JSONEncoder().encode(Image(image:im))
let decoded = try! JSONDecoder().decode(Image.self, from: encoded)
assert(im.pngData()! == decoded.image.pngData()!)
print("ok") // yep

这篇关于编码/解码时 UIImage 不等效的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆