Great question!
Your code works the way that it does because the cod value in decoder.decode(cod, from: data) represents a concrete (specific) type. objc_copyClassList returns to you a list of all classes known to the Objective-C runtime, as objects; within the compactMap, $0 is a metatype, or an object that dynamically represents a type. At the callsite, there is enough type information to make the call: you are passing in a (1) concrete type, which (2) is known to conform to Decodable.
Effectively, the code is attempting every known MyDecodable type: decoder.decode(MyBaseClass.self, ...), decoder.decode(MyClassOne.self, ...), etc. Note that more than one succeeds in decoding, because the structure of the objects in the data match more than one of the types.
However, you can't write something like try decoder.decode(MyDecodable.self, from: data) to achieve the same thing, because there isn't enough information to go off of: the compiler can't try every possible class like you are, because (besides being horribly wasteful) what if there's more than one result?
There's also the matter of MyDecodable requiring AnyObject conformance, and the usage of the Objective-C runtime to list classes. On Darwin platforms (macOS, iOS, etc.), Swift classes are inherently also Objective-C classes, so you can look them up by calling objc_copyClassList; but:
- On platforms which don't have Objective-C support (e.g., Linux, and Windows), this type of lookup isn't possible
- If you were to drop the object requirement via
AnyObject, you wouldn't be able to pick up any struct or enum types that conformed to MyDecodable, only class ones
Theoretically, if you can guarantee that neither of these things will ever be an issue (you never plan to support non-Darwin platforms, and you never plan on having non-object types conform to MyDecodable), then you can keep doing what you're doing here — but as the number of types conforming to MyDecodable grows, this becomes increasingly inefficient: you will be trying every single type on every single decode call (and then possibly trying to figure out what to do if there's more than one successful decode).
Instead, if you're in control of the encoding/decoding format, it's a much better idea to insert some form of marker into your data to indicate what you should do. For example:
[
{ "variant": 1, // decode `data` as `MyClassOne`
"data": { ... } },
{ "variant": 3, // decode `data` as `MyClassThree`
"data": { ... } },
]
This is just a naive example of how to include that information, but having some form of reliable way to look at the data and figure out "what concrete type do I decode?" is ideal.
The specifics will depend on your actual use-case.