The problem is that while instances of [MyDto]
can be freely converted to [MappableProtocol]
and [Any]
, these are really just magical conversions that the compiler does behind the scenes (see this Q&A for more information).
The same conversions don't exist for metatype values, which is why Swift says that a [MyDto].Type
is not a [MappableProtocol].Type
nor a [Any].Type
– they are unrelated metatype types.
Likely the simplest solution in your case would just be to forget working with metatypes, and instead just declare different overloads of test(a:)
to handle different ResponseType
types.
// 'default' overload of test(a:)
func test<T : ResponseProtocol>(a: T) -> String {
return "notFound"
}
func test<T : ResponseProtocol>(a: T) -> String where T.ResponseType == String {
return "String"
}
func test<T : ResponseProtocol>(a: T) -> String where T.ResponseType : MappableProtocol {
return "MappableProtocol"
}
func test<T : ResponseProtocol>(a: T) -> String where T.ResponseType == [MyDto] {
return "Array<MDto>"
}
// overload of test(a:) that accepts a type that conforms to ResponseProtocol, where the
// ResponseType is an Array with arbitrary Element type.
func test<T : ResponseProtocol, ResponseTypeElement : MappableProtocol>(a: T) -> String
where T.ResponseType == [ResponseTypeElement]
{
return "Array<MappableProtocol>"
}
print(test(a: A1())) // String
print(test(a: A2())) // MappableProtocol
print(test(a: A3())) // Array<MyDto>
// A MappableProtocol with a ResponseType that conforms to MappableProtocol,
// but isn't MyDto.
class Foo : MappableProtocol { required init(map: String) { } }
class A4 : ResponseProtocol { typealias ResponseType = [Foo] }
print(test(a: A4())) // Array<MappableProtocol>
(I removed your API
class just to simplify things)
The compiler will simply resolve which overload to call at compile time, rather than having the runtime jump through lots of type-casting hoops.
If you insist on working with metatype values, one possible solution is to define a dummy protocol for Array
to conform to (see for example this similar Q&A), which we can then cast the metatype values to. We can then declare an elementType
static requirement in order to extract the Array
's Element.self
metatype value, which we can then inspect the type of in order to determine what the array is convertible to.
For example, if we define and conform Array
to _ArrayProtocol
:
protocol _ArrayProtocol {
static var elementType: Any.Type { get }
}
extension Array : _ArrayProtocol {
static var elementType: Any.Type {
return Element.self
}
}
We can now use test(a:)
like so:
func test<T : ResponseProtocol>(a: T) -> String {
if T.ResponseType.self is String.Type {
return "String"
}
if T.ResponseType.self is MappableProtocol.Type {
return "MappableProtocol"
}
// attempt to cast the T.ResponseType.self metatype value to the existential metatype
// type _ArrayProtocol.Type (i.e a type that conforms to _ArrayProtocol),
// in this case, that's only ever Array.
if let responseType = T.ResponseType.self as? _ArrayProtocol.Type {
// switch on the element type, attempting to cast to different metatype types.
switch responseType.elementType {
case is MyDto.Type:
return "Array<MyDto>"
case is MappableProtocol.Type:
return "Array<MappableProtocol>"
default:
return "Array<Any>"
}
}
return "notFound"
}
print(test(a: A1())) // String
print(test(a: A2())) // MappableProtocol
print(test(a: A3())) // Array<MyDto>
print(test(a: A4())) // Array<MappableProtocol>