Protocol type cannot conform to protocol because only concrete types can conform to protocols
Asked Answered
S

3

32

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 as Codable which also implement Decodable. Any help would be highly appreciated!

Snakebite answered 14/7, 2019 at 10:17 Comment(4)
The 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 to Codable.Asta
@Asta 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 wroteSnakebite
Your code contains two concrete types, StickerString and StickerBitmap.Asta
@Asta I added associatedtype Inside Sticker and within StickerString and StickerBitmap I'm assigning their own types in the typealias, 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?Snakebite
A
27

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
    }
}
Asta answered 14/7, 2019 at 10:45 Comment(7)
Hey Vadian! Thank you for the detailed response. The question is if it's possible to combine both 'StickerString' and 'StickerBitmap' in the same array?Snakebite
From 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?Snakebite
Cool! 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 typeSnakebite
The array must look like [.string(StickerString()), .image(StickerBitmap())]. In cellForRow switch on the enum like in the encode method.Asta
WORKS!!!! 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.Snakebite
In conjunction with Codable it's the most efficient way. Without Codable the simpler protocol solution as common type is sufficientAsta
instead of DecodingError.keyNotFound should be DecodingError.typeMismatchPelorus
G
1

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 */
            }
        }
    }
Gildagildas answered 14/7, 2019 at 10:47 Comment(2)
Hey 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?Snakebite
I'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 decodesGildagildas
S
0

The Sticker protocol is not confirming/implementing the Codable, it is actually inheriting from the Codable. As the error message suggests, Protocols do not conform to other protocols only concrete types do.

protocol Sticker: Codable //This is Protocol inheritance

By stating

public struct StickerString: Sticker

Means that Sticker string conforms to Sticker and the Sticker is child of Codable, so StickerString eventually conforms to Codable. There is no need to state conformance again i.e:

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

Now coming to the decoding part.

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

The decode method needs a concrete type. It doesn't have any information about the underlying type or its attributes because Sticker is just a protocol itself inheriting from Codable that has no properties/attributes. Compiler would not have any problem combining both the StrickerString and StickerBitmap after they've decoded e.g.

let stickerString = try JSONDecoder().decode(StickerString.self, from: data)
let stickerBitmap = try JSONDecoder().decode(StickerBitmap.self, from: data)
let stickers : [Sticker] = [stickerString, stickerBitmap]
Surround answered 3/7, 2020 at 10:30 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.