Swift 4 Codable: How to encode/decode an object when only a protocol is known?

Trying to convert from Swift 3 to Swift 4 in Xcode Version 9.0 beta 5 (9M202q)


I have a lot of NSCoding classes at the moment. Many have classes have members known only by protocol. Not a problem for NSCoding. However, I cannot find a way to encode/decode these members by protocol.

Can such data items that are only known by protocol be encoded and decoded? Or must I stick with NSCoding for now?


A short example, below. The line trying to encode someData, whose type is unknown but is known to be SomeProtocol, gets an error complaining that it cannot be invoked with the given arglist. The line trying to decode someData gives an error: No 'decode' candidates produce the expected contextual result type 'SomeProtocol'


protocol SomeProtocol: Codable {

var someData: Int { get }

}

class SomeClass: SomeProtocol {

let someData: Int = 9999

}

class SomeMore: Codable {

var someData: SomeProtocol = SomeClass()

required init(from decoder: Decoder) throws {

let container = try decoder.container(keyedBy: CodingKeys.self)

someData = try container.decode(SomeProtocol.self, forKey: CodingKeys.someData)

}

func encode(to encoder: Encoder) throws {

var container = encoder.container(keyedBy: CodingKeys.self)

container.encode(someData, forKey: CodingKeys.someData)

}

enum CodingKeys: CodingKey {

case someData

}

}

Replies

There isn't enough information in your encoded archive to decode the data properly. You cannot create an instance of "SomeProtocol", so you need to know the actual class of the data, so that you can create an instance of a concrete type. (Well, pass the concrete type into the "decode", rather.)


I'm not sure, offhand, how the Swift compiler team intends us to handle cases like this. (Keep in mind that there is a security hole if you allow unarchived data to tell you what its class is. This is something that was addressed by NSSecureCoding on the Obj-C side, and I suspect this is why you have to be explicit in Swift.)


You may do better to ask this question over at swift.org on the swift-users mailing list. Someone from the compiler team can jump in and tell you what the expected way of handling this is.

Thanks. That is really what I expected to hear. I have not been using NSSecureCoding in Swift because of the same issue. So I am assuming another issue will arise with Codable as it does with NSSecureCoding: the exact class must be specified to decode, not a base class. Which ***** if what you know is the base class rather than which derived class.


Kinda subverts protocols and class hierarchies at the same time.


So: NSCoding it is!

Swift is more towards strict type compared to Dynamic typing (Objc). Encode/Decode should know which exact concrete class to encode/decode. SomeProtocol isn't concreate class.


I would implement this like below (Using Generics)


class SomeMore <T:SomeProtocol>: Codable {
    var someData: T
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        someData = try container.decode(T.self, forKey: CodingKeys.someData)
    }
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        container.encode(someData, forKey: CodingKeys.someData)
    }
    enum CodingKeys: CodingKey {
        case someData
    }

}


Now SomeMore class knows which Concreate class will be for the someData variable.

Pass SomeMore<SomeClass>.self whereever JSONEncoder/Decoder is used.


Hope this helps.