【问题标题】:UIImage not equivalent when encoding/decoding编码/解码时 UIImage 不等效
【发布时间】:2021-09-09 05:25:53
【问题描述】:

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

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

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

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

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 一起工作:

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 如下所示:

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 一致性在UIImage 属性上使用isEqual(_:)they say 是“确定两个图像是否包含相同图像数据的唯一可靠方法。”

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

【问题讨论】:

    标签: json swift uiimage jsondecoder equatable


    【解决方案1】:

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

    他们错了。过去那段文档也误导了我!

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


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

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

    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
    

    【讨论】:

    • 我明白了。然而,它仍然不起作用!我将测试更改为:func testProfileImageCodable() throws { let testImage = UIImage(systemName: "applelogo")! let encoded = try JSONEncoder().encode(ProfileImage(testImage)) let decoded = try JSONDecoder().decode(ProfileImage.self, from: encoded) XCTAssertTrue(decoded.image.pngData() == testImage.pngData()) } ,但它仍然失败。也许这是我如何实现 UIImage 的编码/解码的问题? (抱歉格式化,不向 SO cmets 添加完整代码很痛苦)
    • 这是一个gist,如果您更容易阅读,请提供相关代码。
    • 是的,我明白了,你已经丢弃了scale 信息。
    • 是的,你是对的,UIImage(systemName: "applelogo")! 只是一个占位符,我将在我的测试包中捆绑一个模拟图像。 documentation 表示“如果您从名称包含 @2x 修饰符的文件中加载图像,则比例设置为 2.0。您还可以在从 Core Graphics 图像初始化图像时指定显式比例因子。所有其他假定图像的比例因子为 1.0。"
    • 因此我假设用户照片库中的 UIImage 的比例为 1.0——但可能在设备之间有所不同,就像 Asset.xcassets 中一样。为了完整起见,我认为你是对的——我也会对 scale 属性进行编码/解码。
    猜你喜欢
    • 1970-01-01
    • 2020-11-19
    • 1970-01-01
    • 2016-08-10
    • 2014-01-02
    • 1970-01-01
    • 1970-01-01
    • 2020-05-27
    • 2012-02-17
    相关资源
    最近更新 更多