swift 自定义结构:类型不符合协议“可解码”

waxmsbnn  于 2023-01-29  发布在  Swift
关注(0)|答案(3)|浏览(210)

我希望能够保存一个Custom-structUserDefaults,但为此我需要它是Codable ..我尝试这样:

struct Wishlist: Codable {
var name: String
var image: UIImage
var wishData: [Wish]
var color: UIColor
var textColor: UIColor
var index: Int
}

但这会得到error
类型“愿望单”不符合协议“可解码”
下面是我的Class Wish,也许这就是问题所在:

class Wish: NSObject {
public var wishName : String?
public var checkedStatus : Bool?
public var wishLink : String?
public var wishPrice : String?
public var wishNote : String?
public var wishImage : UIImage?

init(withWishName name: String, link: String, price: String, note: String, image: UIImage, checked: Bool) {
    super.init()
    wishName = name
    checkedStatus = checked
    wishLink = link
    wishPrice = price
    wishNote = note
    wishImage = image
}
}

我到底做错了什么?

cqoc49vn

cqoc49vn1#

您需要使Wish采用Codable
但是因为UIImageUIColor不是Codable,所以必须手动实现它们,如Encoding and Decoding Custom Types中所述:

struct Wishlist: Codable {
    var name: String
    var image: UIImage
    var wishes: [Wish]
    var color: UIColor
    var textColor: UIColor
    var index: Int

    init(name: String, image: UIImage, wishes: [Wish], color: UIColor, textColor: UIColor, index: Int) {
        self.name = name
        self.image = image
        self.wishes = wishes
        self.color = color
        self.textColor = textColor
        self.index = index
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)

        name = try values.decode(String.self, forKey: .name)
        wishes = try values.decode([Wish].self, forKey: .wishData)
        color = try values.decode(Color.self, forKey: .color).uiColor
        textColor = try values.decode(Color.self, forKey: .textColor).uiColor
        index = try values.decode(Int.self, forKey: .index)

        let data = try values.decode(Data.self, forKey: .image)
        guard let image = UIImage(data: data) else {
            throw DecodingError.dataCorruptedError(forKey: .image, in: values, debugDescription: "Invalid image data")
        }
        self.image = image
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)

        try container.encode(name, forKey: .name)
        try container.encode(wishes, forKey: .wishData)
        try container.encode(Color(uiColor: color), forKey: .color)
        try container.encode(Color(uiColor: textColor), forKey: .textColor)
        try container.encode(index, forKey: .index)
        try container.encode(image.pngData(), forKey: .image)
    }

}

struct Wish: Codable {
    public var name: String
    public var checkedStatus: Bool
    public var link: String
    public var price: String
    public var note: String
    public var image: UIImage

    init(name: String, link: String, price: String, note: String, image: UIImage, checkedStatus: Bool) {
        self.name = name
        self.checkedStatus = checkedStatus
        self.link = link
        self.price = price
        self.note = note
        self.image = image
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)

        name = try values.decode(String.self, forKey: .name)
        checkedStatus = try values.decode(Bool.self, forKey: .checkedStatus)
        link = try values.decode(String.self, forKey: .link)
        price = try values.decode(String.self, forKey: .price)
        note = try values.decode(String.self, forKey: .note)

        let data = try values.decode(Data.self, forKey: .image)
        guard let image = UIImage(data: data) else {
            throw DecodingError.dataCorruptedError(forKey: .image, in: values, debugDescription: "Invalid image data")
        }
        self.image = image
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)

        try container.encode(name, forKey: .name)
        try container.encode(checkedStatus, forKey: .checkedStatus)
        try container.encode(link, forKey: .link)
        try container.encode(price, forKey: .price)
        try container.encode(note, forKey: .note)
        try container.encode(image.pngData(), forKey: .image)
    }
}

这里我将使用这个作为编码UIColor对象的一种方便的方法:

struct Color: Codable {
    let red: CGFloat
    let green: CGFloat
    let blue: CGFloat
    let alpha: CGFloat

    init(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
        self.red = red
        self.green = green
        self.blue = blue
        self.alpha = alpha
    }

    init(uiColor: UIColor) {
        var red: CGFloat = 0
        var green: CGFloat = 0
        var blue: CGFloat = 0
        var alpha: CGFloat = 0

        uiColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha)

        self.red = red
        self.green = green
        self.blue = blue
        self.alpha = alpha
    }

    var uiColor: UIColor { UIColor(red: red, green: green, blue: blue, alpha: alpha) }
}

注意,我做了一些不相关的更改:

  • 我创建了这两个struct,除非必要,否则我不会引入引用类型(更不用说NSObject子类了)。
  • 我简化了一些属性名。例如,在Wish中,我们一般不会在属性名中使用wish前缀。我也不会在属性名中使用“data”,除非它实际上是Data
  • 我更新了init方法以使用标准命名约定。

顺便说一下,另一种方法是完全避免在模型类型中使用UIKit类型。这完全消除了对自定义编码器/解码器的需要。并且您可以拥有特定于平台的扩展,这些扩展提供了必要的便利初始化器,可以桥接到UIKit类型。例如:

// MARK: - Wishlist

struct Wishlist: Codable {
    let name: String
    let imageData: Data     // rather than `UIImage`
    let wishes: [Wish]
    let color: Color        // rather than `UIColor`
    let textColor: Color    // rather than `UIColor`
    let index: Int
}

// MARK: Wishlist UIKit extension

#if os(iOS)
extension Wishlist {
    init(name: String, image: UIImage, wishes: [Wish], color: UIColor, textColor: UIColor, index: Int) {
        self.init(
            name: name,
            imageData: image.pngData()!,
            wishes: wishes,
            color: Color(uiColor: color),
            textColor: Color(uiColor: textColor),
            index: index
        )
    }

    var image: UIImage? { UIImage(data: imageData) }
}
#endif

// MARK: - Wish

struct Wish: Codable {
    let name: String
    let checkedStatus: Bool
    let link: URL           // rather than `String`
    let price: String
    let note: String
    let imageData: Data     // rather than `UIImage`
}

// MARK: Wish UIKit extension

#if os(iOS)
extension Wish {
    init(name: String, link: URL, price: String, note: String, image: UIImage, checkedStatus: Bool) {
        self.init(
            name: name,
            checkedStatus: checkedStatus,
            link: link,
            price: price,
            note: note,
            imageData: image.pngData()!
        )
    }

    var image: UIImage? { UIImage(data: imageData) }
}
#endif

注意,我不仅从模型类型中删除了UIColor,而且还删除了UIImage。现在,在上面我将UIImage移到了Data,但是实际上,图像负载可能根本不属于这个模型类型。您应该只有图像URL或资产标识符,并且将图像获取和存储与模型一起解耦。(因为图像和模型对象往往具有非常不同的存储器特性,你经常希望在需要的时候获取图像,并将其存储在某个可刷新的缓存中,但这超出了这个问题的范围。)

cwtwac6a

cwtwac6a2#

在您的情况下,您应该添加CodingKeys枚举,而不是使用UIColorUIImage数据类型。我以前遇到过相同的错误,但后来我意识到CodingKey与结构不匹配,也没有non-codable数据类型。只需将数据类型更改为您的自定义可编码对象。
错误示例:

public struct DtClip: Codable {

    // MARK: Properties
    public var video: String?
    public var preview: String?
    public var clip: String?
    public var trailer: Any?

    enum CodingKeys: String, CodingKey {
        case video = "video"
        case preview = "preview"
        case clip = "clip"
    }
}

从示例中我们知道trailer还没有在codingKeys中。您应该将所有属性添加到CodingKeys。并且Any数据类型应该更改为可编码数据类型,如StringIntTrailer(自定义可编码数据类型)。下面是正确的示例:

public struct DtClip: Codable {

    // MARK: Properties
    public var video: String?
    public var preview: String?
    public var clip: String?
    public var trailer: Trailer?

    enum CodingKeys: String, CodingKey {
        case video = "video"
        case preview = "preview"
        case clip = "clip"
        case trailer = "trailer"
    }
}

public struct Trailer: Codable {

    // MARK: Properties
    public var name: String?
    public var id: Int?

    enum CodingKeys: String, CodingKey {
       case name, url
    }
}
n9vozmp4

n9vozmp43#

UIImage不符合Codable。您可以先将其转换为Base64,然后将 that 存储在UserDefaults中。

相关问题