协议类型不能符合协议,因为只有具体类型才能符合协议

Posted

技术标签:

【中文标题】协议类型不能符合协议,因为只有具体类型才能符合协议【英文标题】:Protocol type cannot conform to protocol because only concrete types can conform to protocols 【发布时间】:2019-11-23 08:56:09 【问题描述】:

在应用程序中,我们有两种类型的贴纸,字符串和位图。每个贴纸包可以包含两种类型。这就是我声明模型的方式:

// Mark: - Models

protocol Sticker: Codable 


public struct StickerString: Sticker,  Codable, Equatable 
    let fontName: String
    let character: String


public struct StickerBitmap: Sticker,  Codable, Equatable 
    let imageName: String

在用户选择并使用了一些贴纸后,我们想将这些贴纸保存到UserDefaults,这样我们就可以向他展示“最近使用的”贴纸标签。我正在尝试解码保存的[Sticker] 数组:

let recentStickers = try? JSONDecoder().decode([Sticker].self, from: data)

但我得到以下编译错误:

Protocol type 'Sticker' cannot conform to 'Decodable' because only concrete types can conform to protocols

我不明白为什么我将Sticker 声明为Codable,它也实现了Decodable。任何帮助将不胜感激!

【问题讨论】:

这个错误告诉你到底出了什么问题:一个协议不能符合一个协议。 decode 的第一个参数必须是具体类型。一种解决方案是使用限制为 Codable 的泛型类型。 @vadian 嘿 Vadian!感谢你的回复。可能是我的英语水平不够。我不明白“具体类型”是什么意思。我会尝试像你写的那样想出一个通用类型的解决方案 您的代码包含两种具体类型,StickerStringStickerBitmap @vadian 我在StickerStickerStringStickerBitmap 中添加了associatedtype 我在typealias 中分配了自己的类型,但它给了我同样的错误。我可以声明一个[Sticker] 数组还是必须是其中一种具体类型? 【参考方案1】:

而不是协议使用泛型。

声明一个简单的函数

func decodeStickers<T : Decodable>(from data : Data) throws -> T

    return try JSONDecoder().decode(T.self, from: data)

T 可以是单个对象,也可以是数组。


在你的结构中删除Sticker 协议。您也可以删除 Equatable,因为它是在结构中合成的。

public struct StickerString : Codable 
    let fontName: String
    let character: String


public struct StickerBitmap : Codable 
    let imageName: String


要解码其中一种贴纸类型,请注释类型

let imageStickers = """
["imageName":"Foo","imageName":"Bar"]
"""    
let stickerData = Data(imageStickers.utf8)

let recentStickers : [StickerBitmap] = try! decodeStickers(from: stickerData)
print(recentStickers.first?.imageName)

let stringSticker = """
"fontName":"Times","character":"?"
"""    
let stickerData = Data(stringSticker.utf8)

let sticker : StickerString = try! decodeStickers(from: stickerData)
print(sticker.character)

要解码StickerStringStickerBitmap 类型的数组,请声明具有关联值的包装枚举

enum Sticker: Codable 

    case string(StickerString)
    case image(StickerBitmap)

    init(from decoder: Decoder) throws 
        let container = try decoder.singleValueContainer()
        do 
            let stringData = try container.decode(StickerString.self)
            self = .string(stringData)
         catch DecodingError.keyNotFound 
            let imageData = try container.decode(StickerBitmap.self)
            self = .image(imageData)
        
    

    func encode(to encoder: Encoder) throws 
        var container = encoder.singleValueContainer()
        switch self 
            case .string(let string) : try container.encode(string)
            case .image(let image) : try container.encode(image)
        
    

然后就可以解码了

let stickers = """
["imageName":"Foo","imageName":"Bar", "fontName":"Times","character":"?"]
"""

let stickerData = Data(stickers.utf8)
let recentStickers = try! JSONDecoder().decode([Sticker].self, from: stickerData)
print(recentStickers)

在表视图中,枚举上只是 switch

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell 
    let sticker = stickers[indexPath.row]
    switch sticker 
    case .string(let stringSticker): 
        let cell = tableView.dequeueReusableCell(withCellIdentifier: "StringStickerCell", for: indexPath) as! StringStickerCell
        // update UI
        return cell
    case .image(let imageSticker): 
        let cell = tableView.dequeueReusableCell(withCellIdentifier: "ImageStickerCell", for: indexPath) as! ImageStickerCell
        // update UI
        return cell
    

【讨论】:

嘿瓦迪安!感谢您的详细回复。问题是是否可以将 'StickerString' 和 'StickerBitmap' 组合在同一个数组中? 根据我从您的回答中了解到的情况,我们可以使用泛型来解码/编码我的数据。但我试图理解一些事情:最终,当我们将数据解码回贴纸时,我需要选择在同一个数组中使用两种类型的贴纸,因为用户可以从多个贴纸包中选择贴纸。如果我们在这两种贴纸类型之间没有关系,我们怎么能做到这一点? 酷!它正在编译:)!我唯一苦苦挣扎的是,如果我们有一个数组 [Sticker] 并且我想从枚举中获取关联的值(在显示最近的 Stickers 时,我使用 [Sticker] 数组作为数据源,并且取决于我显示不同单元格的数据类型)。我将如何实现这一目标?由于这两种类型没有任何共同点,所以我无法返回共享类型 数组必须看起来像[.string(StickerString()), .image(StickerBitmap())]。在cellForRow 中打开枚举,就像在encode 方法中一样。 结合Codable 是最有效的方式。没有Codable,作为通用类型的更简单的协议解决方案就足够了【参考方案2】:

这里发生的事情是不言自明的


JSONDecoder().decode(/* swift here is expecting class or struct that conforms to Codable */.self, from: data)

但是让我们假设你可以通过一个协议 在你的协议中

protocol Sticker: Codable 

您期望 swift 从数据中解码的属性在哪里?

你在

中添加了属性
public struct StickerString: Sticker,  Codable, Equatable  // it should have redundendant conformance as well as you are conforming to Coddle again
    let fontName: String // here is the properties you are expected to be decoded with the coding keys
    let character: String // here is the properties you are expected to be decoded with the coding keys

我建议你这样做,只要你想要解码的类型是动态的

class GenericService< /* here you can pass your class or struct that conforms to Codable */ GenericResponseModel: Codable> 

func buildObjectFromResponse(data: Data?) -> GenericResponseModel? 
        var object : GenericResponseModel?
        do 
            object = try JSONDecoder().decode(GenericResponseModel.self , from: data!)
         catch (let error)
            print(error)
        
        return object
    


通过这个类,您可以传递任何符合 Codable 的类型甚至列表 然后您将使用以下方法将类型检查与解码过程解耦
private func handleDecodingTypes (stickers: [Sticker])
        for sticker in stickers 
            if sticker is StickerString 
                /* do the decoding here */
            
            if sticker is StickerBitmap 
                /* do the decoding here */
            
        
    

【讨论】:

嘿,卡雷姆!谢谢您的回答。根据我从您的回答和 Vadian 的理解,我们可以使用泛型来解码/编码我的数据。但我试图理解一些事情:最终,当我们将数据解码回 Stickers 时,我需要选择在同一个数组中使用两种 类型的贴纸,用户可以选择来自多个贴纸包的贴纸。如果我们在这两种贴纸类型之间没有关系,我们怎么能做到这一点? 我已经编辑了答案,我的主要目标是将对象转换为特定具体类型的过程和解码过程解耦,因为解码过程不应该知道它的内容解码【参考方案3】:

Sticker 协议并没有确认/实现 Codable,它实际上是从 Codable 继承的。正如错误消息所暗示的那样,协议不符合其他协议,只有具体类型才符合。

protocol Sticker: Codable //This is Protocol inheritance

通过陈述

public struct StickerString: Sticker

表示 Sticker 字符串符合 Sticker 且 Sticker 是 Codable 的子级,因此 StickerString 最终符合 Codable。无需再次声明符合性,即:

public struct StickerString: Sticker,  Codable //Conformance to Codable is redundant

现在进入解码部分。

  let recentStickers = try? JSONDecoder().decode([Sticker].self, from: data)

decode 方法需要一个具体的类型。它没有关于底层类型或其属性的任何信息,因为 Sticker 只是一个从 Codable 继承的协议本身,没有任何属性/属性。 编译器在解码后将 StrickerString 和 StickerBitmap 组合起来不会有任何问题,例如

let stickerString = try JSONDecoder().decode(StickerString.self, from: data)
let stickerBitmap = try JSONDecoder().decode(StickerBitmap.self, from: data)
let stickers : [Sticker] = [stickerString, stickerBitmap]

【讨论】:

以上是关于协议类型不能符合协议,因为只有具体类型才能符合协议的主要内容,如果未能解决你的问题,请参考以下文章

请帮忙:类型'()'不能符合'View';只有结构/枚举/类类型可以符合协议

使用 if 语句时:类型 '()' 不能符合 'View';只有结构/枚举/类类型可以符合协议

“类型‘()’不能符合‘视图’;只有结构/枚举/类类型可以符合协议”

类型“Favorites.Type”不能符合“Encodable”;只有结构/枚举/类类型可以符合协议

类型 '()' 不能符合 'View';只有 struct/enum/class 类型可以符合使用 swift ui 调用调用函数的协议

type() 不能符合 View;只有结构/枚举/类类型可以符合协议