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

43,112

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 */
            }
        }
    }
Share:
43,112

Related videos on Youtube

Roi Mulia
Author by

Roi Mulia

Updated on July 09, 2022

Comments

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

    • vadian
      vadian almost 5 years
      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.
    • Roi Mulia
      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
      vadian almost 5 years
      Your code contains two concrete types, StickerString and StickerBitmap.
    • Roi Mulia
      Roi Mulia almost 5 years
      @vadian 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?
  • Roi Mulia
    Roi Mulia almost 5 years
    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?
  • Roi Mulia
    Roi Mulia almost 5 years
    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?
  • Roi Mulia
    Roi Mulia almost 5 years
    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?
  • Roi Mulia
    Roi Mulia almost 5 years
    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 type
  • vadian
    vadian almost 5 years
    The array must look like [.string(StickerString()), .image(StickerBitmap())]. In cellForRow switch on the enum like in the encode method.
  • Roi Mulia
    Roi Mulia almost 5 years
    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.
  • karem_gohar
    karem_gohar almost 5 years
    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 decodes
  • vadian
    vadian almost 5 years
    In conjunction with Codable it's the most efficient way. Without Codable the simpler protocol solution as common type is sufficient
  • barola_mes
    barola_mes over 2 years
    instead of DecodingError.keyNotFound should be DecodingError.typeMismatch