1
votes

I wish to create a class which will store a Date and any object that conforms to the Codable protocol. I would like this class to also conform to the Codable protocol itself.

I can do this for one object as follows:

class CodableContainerA: NSObject, Codable {
    var date: Date?
    var item: CodableTypeA?
}

I'd rather not have to create a separate CodableContainerX for each CodableTypeX I have.

My current workaround is to create a class called CodableBaseClass that conforms to Codable, derive every CodableType from it and define my class as follows:

class CodableContainer: NSObject, Codable {
    var date: Date?
    var item: CodableBaseClass?
}

This seems a long way around and feels like something I should be able to do by making the CodableTypes conform to a protocol, but I'm not sure how. If I define item to be of type Codable? or (Any & Codable)? I get an error stating

Type 'CodableContainer' does not conform to protocol 'Decodable'

I'm using Swift 4.

Any help or advice would be gratefully accepted. Many thanks,

2

2 Answers

4
votes

This is what generic classes are for:

class CodableContainer<T: Codable>: NSObject, Codable {
    var date: Date?
    var item: T?
}
4
votes

Whenever you find yourself wondering about a separate class for each underlying type, think "generics". E.g.

struct CodableContainer<T: Codable>: Codable { 
    let date: Date
    let item: T
}
  • I'd use struct unless you needed class for some specific reason.
  • I'd make the properties immutable unless you needed them mutable for some reason.
  • I wouldn't subclass NSObject unless there was some specific reason that this particular container needed to be NSObject subclass.
  • I'd make the properties non-optional unless you absolutely needed them to be optional.

But edit this as you see fit. The key point is the generic pattern.


You might want a decoding function that decodes using some consistent DateDecodingStrategy, e.g.

extension CodableContainer {
    static func decodeJSON(_ data: Data) throws -> CodableContainer {
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .iso8601
        return try decoder.decode(self, from: data)
    }
}

Obviously, use whatever dateDecodingStrategy you want. But then you can decode with:

let result = try CodableContainer<Foo>.decodeJSON(data)