I'm trying to create a class that can save different closures (or methods) with an argument of a specific subtype of Decodable
that should be called later. This way I can predefine what actions, or methods, can be called on that class in response to some input. For example, the line addCallback(setOption(_:), SetOptionRequest.self)
should result in the subsequent call to try! performCallback("setOption", JSONEncoder().encode(SetOptionRequest())
to call setOption(data)
where the argument data
has type SetOptionRequest
.
Here is the code I have so far (I took the bit about DecodableWrapper
from here). The problem is that at runtime the cast callback.callback as! (ActionRequest) throws -> Void
fails, since the type of the closure is not (ActionRequest) throws -> Void
but (SetOptionRequest) throws -> Void
. But I have no idea if and how I can cast the closure back to its original type. I considered using Selector
s but I would like to keep the compile-time check that I'm binding methods with their correct argument type.
struct DecodableWrapper: Decodable {
static var baseType: ActionRequest.Type!
var base: ActionRequest
init(from decoder: Decoder) throws {
self.base = try DecodableWrapper.baseType.init(from: decoder)
}
}
open class Server {
private var actionCallbacks = [String: (callback: Any, dataType: ActionRequest.Type)]()
open func setup() {
addCallback(setOption, action: SetOptionRequestResponse.self)
}
public func addCallback<T: ActionRequest>(_ callback: @escaping (_ data: T) throws -> Void, action: T.Type) {
actionCallbacks[T.action] = (callback, T.self)
}
private func performCallback(action: String, data: Data) throws {
let callback = actionCallbacks[action]!
DecodableWrapper.baseType = callback.dataType
let data = try! JSONDecoder().decode(DecodableWrapper.self, from: data).base
try (callback.callback as! (ActionRequest) throws -> Void)(data)
}
private func setOption(_ data: SetOptionRequest) {
}
}
protocol ActionRequest {
static var action: String
}
struct Request: SetOptionRequest {
}