There isn’t a way with the Swift language itself to mimic Codable, because Codable’s implementation relies on the compiler generating special-case code. Specifically, there is no protocol extension that creates the default CodingKeys enum, the compiler creates that enum inside a type that conforms to Codable automatically, unless you specify it yourself.
This is similar to how the Swift compiler will automatically create an initializer for structs (the “memberwise initializer”) unless you specify your own initializer. In that case as well, there is no protocol extension or Swift language feature you can use to replicate the auto-generated struct initializer, because it is based on metaprogramming / code generation, in this case, by the compiler.
There are tools, such as Sourcery (https://github.com/krzysztofzablocki/Sourcery), which allow you to implement your own metaprogramming and code generation. With Sourcery, you could run a script in your build phase that would automatically generate the code for the Command
enum you want, and add it to any type that conforms toCommandHandler
.
This would essentially mimic how Codable works via the Swift compiler generating needed code. But in neither case is it accomplished via Swift language features like protocol extensions, etc. Rather, it is boilerplate source code that gets written by a script rather than having to be written by hand.
UPDATE FOR REVISED QUESTION
If simply ensuring there is a way to enumerate all the cases of the CommandIds enum is all that you need, you can always add a protocol requirement to the CommandId
protocol like this:
protocol CommandId {
static var all: [Self] { get }
}
Then implementations would need to look like:
class HandlerA : CommandHandler {
enum CommandIds : String, CommandId {
case commandA1
case commandA2
static var all: [CommandIds] { return [.commandA1, .commandA2] }
}
}
And your process function could look like:
func processHandler<T:CommandHandler>(_ handler:T){
T.CommandIds.all.forEach { // Do something with each command case }
}
It's worth continuing to note though, that for Codable, Swift does not have or use any language functionality to enumerate all cases. Instead, the compiler uses knowledge of all the properties of the Codable-conforming type to generate a specific implementation of the init(from decoder: Decoder)
for that type, including a line for each case, based on the known property names and types, e.g.
// This is all the code a developer had to write
struct Example: Codable {
let name: String
let number: Int
}
// This is all source code generated by the compiler using compiler reflection into the type's properties, including their names and types
extension Example {
enum CodingKeys: String, CodingKey {
case name, number
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(String.self, forKey: .name)
number = try values.decode(Int.self, forKey: .number)
}
}
Since Swift is a mostly static language with extremely limited runtime reflection (for now), there is no way to do these types of tasks at runtime using language features.
But there is nothing stopping you or any developer from using code generation the same way the Swift compiler does to accomplish similar conveniences. In fact, a well-known member of the Swift core team at Apple even encouraged me to do so when I presented him some challenges I was facing at WWDC.
It's also worth noting that features that are now part of the Swift compiler or have open pull requests to be added to the Swift compiler (like Codable, and automatic conformance to Equatable and Hashable) were first created and implemented in real-world Swift projects using Sourcery, before they were added to Swift itself.