3

I need do different things for different classes. For example I've created the protocols

protocol ResponseProtocol {
    associatedtype ResponseType: Any
}

protocol MappableProtocol {
    init(map: String)
}

and I'm adding my data class MyDto

class MyDto: MappableProtocol {
    required init(map: String) { }
}

and 3 different response classes

class A1: ResponseProtocol {
    typealias ResponseType = String
}

class A2: ResponseProtocol {
    typealias ResponseType = MyDto
}

class A3: ResponseProtocol {
    typealias ResponseType = [MyDto]
}

now I need do different things depending on ResponseType.

I tried this code, but I'm running into problems with Arrays

class API {

    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"
        }


        if T.ResponseType.self is [Any].Type {
            return "Array<Any>" // Why is this false?
        }
        if T.ResponseType.self is [MappableProtocol].Type {
            return "Array<MappableProtocol>" //Why is this false?
        }
        if T.ResponseType.self is [MyDto].Type {
            return "Array<MyDto>" // Why is only this true?
        }
        return "notFound"
    }

}
let api = API()
let t1 = api.test(a: A1())
let t2 = api.test(a: A2())
let t3 = api.test(a: A3())

In my playground console, for array A3, I see Array<MyDto>, but I expected the first return for array like Array<Any>.

How can I check for an Array where the Element is of kind MappableProtocol?

Hamish
  • 78,605
  • 19
  • 187
  • 280
EvGeniy Ilyin
  • 1,817
  • 1
  • 21
  • 38

1 Answers1

4

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>
Community
  • 1
  • 1
Hamish
  • 78,605
  • 19
  • 187
  • 280
  • Can you help me with `Dictionary`. I tied this code, by your example `func sortDescriptor, ValueElement: Any>(a: T) -> String where T.Element.value: ValueElement { return "Dictionary " }`. But this not worked – EvGeniy Ilyin Apr 01 '17 at 13:37
  • 1
    @EvGeniyIlyin `Dictionary` is neither a class nor a protocol, so you cannot use it as a generic constraint. It's unclear exactly what you meant for `ValueElement` to be in this context – is it an existing protocol? If so, you probably meant `func sortDescriptor(a: [String : Value]) -> String where Value : ValueElement`. Remember: use generic placeholders to describe the type that *varies* – in this case `Dictionary` is constant, so it's defined as the parameter type, rather than in a generic placeholder. – Hamish Apr 01 '17 at 13:50
  • 1
    If you just meant "a dictionary with a `String` key, but any type of value" – you meant to say `func sortDescriptor(a: [String : Value]) -> String`. – Hamish Apr 01 '17 at 13:52
  • 2
    @EvGeniyIlyin But really, if you have a new problem – you should [ask a new question](http://stackoverflow.com/questions/ask) (after, of course, first doing some research) rather than asking it as a follow-up question in the comments. – Hamish Apr 01 '17 at 13:53