协议类型不能符合协议,因为只有具体类型才能符合协议
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!感谢你的回复。可能是我的英语水平不够。我不明白“具体类型”是什么意思。我会尝试像你写的那样想出一个通用类型的解决方案
您的代码包含两种具体类型,StickerString
和 StickerBitmap
。
@vadian 我在Sticker
和StickerString
和StickerBitmap
中添加了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)
要解码StickerString
和StickerBitmap
类型的数组,请声明具有关联值的包装枚举
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 调用调用函数的协议