为 UIColor 实现 Codable

Implementing Codable for UIColor

是否可以为 UIColor

实现 EncodableDecodable 属性

当我尝试添加 Decodable 扩展程序时出现错误

extension UIColor : Decodable {
    public required init(from decoder: Decoder) throws {
        self.init(red: 1, green: 1, blue: 1, alpha: 1)
    }
}

error: ColorStuff.playground:98:21: error: initializer requirement 'init(from:)' can only be satisfied by a required initializer in the definition of non-final class 'UIColor' public required init(from decoder: Decoder) throws {

我是不是漏掉了什么明显的东西?

我对 Encodable 扩展没有任何问题 - 它似乎是一个 Decodable 问题。

错误消息暗示我无法执行此操作,因为无法访问 UIColor class 定义

由于编译器给出的错误,您无法在扩展中使 UIColor 符合 Decodable

一个解决方案是创建一个 Codable 包装器类型并使用它。

既然UIColor已经符合NSCoding,我们就写一个通用类型,这样我们就可以编码和解码任何符合NSCoding.

的东西
import UIKit

struct WrapperOfNSCoding<Wrapped>: Codable where Wrapped: NSCoding {
    var wrapped: Wrapped

    init(_ wrapped: Wrapped) { self.wrapped = wrapped }

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let data = try container.decode(Data.self)
        guard let object = NSKeyedUnarchiver.unarchiveObject(with: data) else {
            throw DecodingError.dataCorruptedError(in: container, debugDescription: "failed to unarchive an object")
        }
        guard let wrapped = object as? Wrapped else {
            throw DecodingError.typeMismatch(Wrapped.self, DecodingError.Context(codingPath: container.codingPath, debugDescription: "unarchived object type was \(type(of: object))"))
        }
        self.wrapped = wrapped
    }

    func encode(to encoder: Encoder) throws {
        let data = NSKeyedArchiver.archivedData(withRootObject: wrapped)
        var container = try encoder.singleValueContainer()
        try container.encode(data)
    }
}

let colors = [UIColor.red, UIColor.brown]
print(colors)
let jsonData = try! JSONEncoder().encode(colors.map({ WrapperOfNSCoding([=10=]) }))
let colors2 = try! JSONDecoder().decode([WrapperOfNSCoding<UIColor>].self, from: jsonData).map({ [=10=].wrapped })
print(colors2)

尽管需要初始化,但仍有方法可以使 UIColor Codable。 您可以扩展 Codable 本身,以便 UIColor 开始自动符合它。

import UIKit

extension Decodable where Self: NSSecureCoding {

    public init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let data = try container.decode(Data.self)
        guard let object = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? Self else {
            throw DecodingError.dataCorruptedError(
                in: container,
                debugDescription: "Invalid object"
            )
        }
        self = object
    }
}

extension Encodable where Self: NSSecureCoding {
    public func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        let data = try NSKeyedArchiver.archivedData(withRootObject: self, requiringSecureCoding: true)
        try container.encode(data)
    }
    
}

extension UIColor: Codable { }

检查一下

import XCTest
class CodingTextCase: XCTestCase {
    let encoder = JSONEncoder()
    let decoder = JSONDecoder()
    
    func testUIColor() throws {
        let colorAsJSON = try encoder.encode(UIColor.red)
        print(String(data: colorAsJSON, encoding: .utf8)!)
        let uiColor = try? decoder.decode(UIColor.self, from: colorAsJSON)
        XCTAssertEqual(uiColor!, UIColor.red)
    }
}
CodingTextCase.defaultTestSuite.run()

但请注意,在这种情况下,UIColor 实例的数据将占用大约 500 个字节并将其存储为 RGBA