Protocol type cannot conform to protocol because only concrete types can conform to protocols
Solution 1
Rather than protocols use generics.
Declare a simple function
func decodeStickers<T : Decodable>(from data : Data) throws -> T
{
return try JSONDecoder().decode(T.self, from: data)
}
T
can be a single object as well as an array.
In your structs drop the Sticker
protocol. You can also delete Equatable
because it's getting synthesized in structs.
public struct StickerString : Codable {
let fontName: String
let character: String
}
public struct StickerBitmap : Codable {
let imageName: String
}
To decode one of the sticker types annotate the type
let imageStickers = """
[{"imageName":"Foo"},{"imageName":"Bar"}]
"""
let stickerData = Data(imageStickers.utf8)
let recentStickers : [StickerBitmap] = try! decodeStickers(from: stickerData)
print(recentStickers.first?.imageName)
and
let stringSticker = """
{"fontName":"Times","character":"😃"}
"""
let stickerData = Data(stringSticker.utf8)
let sticker : StickerString = try! decodeStickers(from: stickerData)
print(sticker.character)
To decode an array of StickerString
and StickerBitmap
types declare a wrapper enum with associated values
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)
}
}
}
Then you can decode
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)
In a table view just switch
on the enum
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
}
}
Solution 2
what happens here is kind of self explanatory
JSONDecoder().decode(/* swift here is expecting class or struct that conforms to Codable */.self, from: data)
but let us assume that you can pass a protocol in your protocol
protocol Sticker: Codable {
}
where is the properties that you are expecting swift to decode from data ?
you added the properties in
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
}
Here is what I suggest you do as long you want the type you want to decode to be dynamic
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
}
}
- through this class you can pass any type or even list that conforms to Codable
- then you will decouple the type checking from the decoding process using the method below
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 */
}
}
}
Related videos on Youtube
![Roi Mulia](https://i.stack.imgur.com/RQbVx.jpg?s=256&g=1)
Roi Mulia
Updated on July 09, 2022Comments
-
Roi Mulia almost 2 years
Within the app, we have two types of Stickers, String and Bitmap. Each sticker pack could contain both types. This is how I declare the models:
// 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 }
After the user chooses some stickers and used them, we want to save the stickers into
UserDefaults
so we can show him the "Recently Used" Sticker tab. I'm trying to Decode the saved[Sticker]
array:let recentStickers = try? JSONDecoder().decode([Sticker].self, from: data)
But I get the following compile error:
Protocol type 'Sticker' cannot conform to 'Decodable' because only concrete types can conform to protocols
I can't understand why as I declared
Sticker
asCodable
which also implementDecodable
. Any help would be highly appreciated!-
vadian almost 5 yearsThe error tells you exactly what's wrong: A protocol cannot conform to a protocol. The first parameter of
decode
must be a concrete type. A solution is to use a generic type constrained toCodable
. -
Roi Mulia almost 5 years@vadian Hey Vadian! Thank you for your reply. Might be a lack of my English skills. I can't understand what 'concrete type' means. I'll try to come up with a generic type solution like you wrote
-
vadian almost 5 yearsYour code contains two concrete types,
StickerString
andStickerBitmap
. -
Roi Mulia almost 5 years@vadian I added
associatedtype
InsideSticker
and withinStickerString
andStickerBitmap
I'm assigning their own types in thetypealias
, but it gives me the same error. Will I be able to declare a[Sticker]
array or It'll have to be either one of the concrete types?
-
-
Roi Mulia almost 5 yearsHey Vadian! Thank you for the detailed response. The question is if it's possible to combine both 'StickerString' and 'StickerBitmap' in the same array?
-
Roi Mulia almost 5 yearsHey Karem! Thank you for your answer. From what I understood from your answer and Vadian one, we could use generics to decode/code my data. But I'm trying to understand something: Eventually, when we are decoding the data back into Stickers, I need to have the option to use both types of stickers in the same array, as the user can choose stickers from multiple sticker packs. If we don't have a relation between the two stickers types, how can we achieve this?
-
Roi Mulia almost 5 yearsFrom what I understood from your answer, we could use generics to decode/code my data. But I'm trying to understand something: Eventually, when we are decoding the data back into Stickers, I need to have the option to use both types of stickers in the same array, as the user can choose stickers from multiple sticker packs. If we don't have a relation between the two stickers types, how can we achieve this?
-
Roi Mulia almost 5 yearsCool! It's compiling:)! The only thing I'm struggling with is that if we have an array [Sticker] and I want to get the associated value from the enum (when displaying the recent Stickers I'm using the [Sticker] array as the data source, and depends on the data type I'm showing a different cell). How would I achieve this? As both types don't have anything in common so I can't return a shared type
-
vadian almost 5 yearsThe array must look like
[.string(StickerString()), .image(StickerBitmap())]
. IncellForRow
switch on the enum like in theencode
method. -
Roi Mulia almost 5 yearsWORKS!!!! Awesome! Wow, I thought it'll be so much simpler when I started, seems to be quiet trivial. I have to know: Do you think there was a better way to approach it? Has it took much more energy than I expected, was basically just a combination of two data-types.
-
karem_gohar almost 5 yearsI've edited the answer and the main goal for me was to decouple the process of casting the object to a specific concrete type and the decoding process as the decoding process should not be aware of the contents it decodes
-
vadian almost 5 yearsIn conjunction with
Codable
it's the most efficient way. WithoutCodable
the simpler protocol solution as common type is sufficient -
barola_mes over 2 yearsinstead of
DecodingError.keyNotFound
should beDecodingError.typeMismatch