2

I have this protocol,

protocol PaginableItem {
   var resultType: ResultType { get set }
}

with a single property of an enum type,

enum ResultType: String, Codable {
   case photo = "PHOTO"
   case baseItem = "BASEITEM"
   case new = "NEW"
}

I also have 3 different structs and classes with conforms to PaginableItem and a class with some PaginableItems,

final class TimelineModel: Codable {
   let stream: String
   let participants: String
   let header1: PaginableList
   let header2: PaginableList
   let items: PaginableList
}

final class PaginableList: Codable {
   let data: [PaginableItem]
   let pagination: Pagination
}

I'm trying to cache TimelineModel using Swift 4 Codable, but I'm not sure the best way to do that. Xcode is complaining about PaginableItem not conforming to Codable.

It is mandatory to implement init(from decoder: Decoder) throws and encode(to encoder: Encoder) throws in ResultType?

Must PaginableItem implement Codable also?

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Christian
  • 382
  • 2
  • 11

2 Answers2

4

PaginableItem can't conform to Codable is because Swift protocols can't conform to themselves.

Source:

Protocol doesn't conform to itself?

Encode/Decode Array of Types conforming to protocol with JSONEncoder

Following the second link above, if you want to encode/decode a PaginableItem you have to type erase them in an AnyPaginableItem class/struct and make that conform to Encodable/Decodable. I've done it like so:

protocol PaginableItem {
    var resultType: ResultType { get set }
}

class AnyPaginableItem: Codable {
    var resultType: ResultType

    init(resultType: ResultType) {
        self.resultType = resultType
    }
}

enum ResultType: String, Codable {
    case photo = "PHOTO"
    case baseItem = "BASEITEM"
    case new = "NEW"
}

struct ContainsPaginableItem: Codable {
    let paginableItem: AnyPaginableItem
}

let paginableItem = AnyPaginableItem(resultType: .photo)
let itemToEncode = ContainsPaginableItem(paginableItem: paginableItem)

let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(itemToEncode)
let jsonString = String(data: jsonData, encoding: .utf8)

This example only encodes the properties that can be declared in PaginableItem, which I assume is all you need.

If you need to encode ALL of the properties of the type conforming to PaginableItem there's a longer solution in the second link above that you can adopt.

mj_jimenez
  • 443
  • 2
  • 5
0

Must PaginableItem implement Codable also?

Absolutely! By making sure PaginableItem conforms to Codable, you maintain overall conformance to Codable for PaginableList.

I notice you had ResultType conform to Codable. However what happens if PaginableItem adds another object which does not - could be the danger.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
FullMetalFist
  • 208
  • 1
  • 2
  • 14
  • I added the protocol `Codable` to `PaginableItem` but Xcode crashes when trying to encode the main `TimelineModel`: `fatal error: Array does not conform to Encodable because PaginableItem does not conform to Encodable.` – Christian Nov 30 '17 at 18:24
  • Something you might want to try is to break up the conformance to Encodable/Decodable protocols- instead of conforming to Codable for the whole thing, create an extension for Encodable & another for Decodable – FullMetalFist Nov 30 '17 at 19:33
  • class Item: PaginableItem { let resultType: ResultType init(resultType: ResultType) { self.resultType = resultType } enum CodingKeys: String, CodingKey { //... } } extension Item: Encodable { func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(resultType, forKey: .resultType) } } – FullMetalFist Nov 30 '17 at 19:52
  • not for protocols, but for classes/structs they can. – FullMetalFist Dec 01 '17 at 11:29
  • is it possible to alter PaginableItem to be a struct, then allow it to conform? – FullMetalFist Dec 01 '17 at 11:43