xaphod
xaphod

Reputation: 6804

How can I decode when I don't know the type, with class inheritance?

I have a base class Action, which is an Operation. It has a bunch of crufty Operation stuff in it (KVO and all that). The base class itself doesn't actually need to encode/decode anything.

class Action : Operation, Codable {
    var _executing = false
    ...
}

I have a bunch of Action sub-classes, like DropboxUploadAction, which are directly instantiated with an Input struct they define:

let actionInput = DropboxUploadAction.Input.init(...)
ActionManager.shared.run(DropboxUploadAction.init(actionInput, data: binaryData), completionBlock: nil)

Here's what the subclasses look like:

class DropboxUploadAction : Action {
    struct Input : Codable {
        var guid: String
        var eventName: String
        var fileURL: URL?
        var filenameOnDropbox: String
        var share: Bool
    }

    struct Output : Codable {
        var sharedFileLink: String?
        var dropboxPath: String?
    }

    var input: Input
    var output: Output

    ...

    required init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        input = try values.decode(Input.self, forKey: .input)
        output = try values.decode(Output.self, forKey: .output)
        let superDecoder = try values.superDecoder()
        try super.init(from: superDecoder)
    }

    fileprivate enum CodingKeys: String, CodingKey {
        case input
        case output
    }

    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(input, forKey: .input)
        try container.encode(output, forKey: .output)
        try super.encode(to: container.superEncoder())
    }
}

When some situations occur such as a loss of internet connectivity, these classes need to be serialized to disk for later. That's fine, because at the time I have references to them and can encode them with JSONEncoder().encode(action), no problem.

But later when I want to deserialize them, I need to specify the type of the class and I don't know what it is. I have some data and I know it can be decoded to a class that inherits from Action, but I don't know which subclass it is. I'm loathe to encode that in the filename. Is there some way to decode it as the base class Action, then in the decode() method of Action, somehow detect the proper class and redirect?

In the past I've used NSKeyedUnarchiver.setClass() to handle this. But I don't know how to do that with Swift 4's Codable, and I understand that NSCoding is deprecated now so I shouldn't use NSKeyedUnarchiver anymore...

If it helps: I have a struct Types : OptionSet, Codable which each subclass returns, so I don't have to use the name of the class as its identity.

Thanks for any help!

Upvotes: 2

Views: 414

Answers (1)

mj_jimenez
mj_jimenez

Reputation: 443

Uhhh NSCoding isn't deprecated. We still use it when instantiating UIViewControllers from storyboard via init(coder:).

Also, if you still don't want to use NSCoding, you can just store the Input, Output and Types to a struct and serialize that to disk instead.

struct SerializedAction {
  let input: Input
  let output: Output
  let type: Type
}

When needed, you can decode that and decide the correct Action to initialize with your input/output via the type property.

class DropboxAction: Action {
  ...
  init(input: Input, output: Output) {
  ...
  }
}

You don't necessarily need to encode the entire Action object.

Upvotes: 1

Related Questions