检查元类型是否为具有类型MyProtocol

时间:2017-03-30 20:31:06

标签: arrays swift generics

我需要为不同的课做不同的事情。例如,我已经创建了协议

protocol ResponseProtocol {
    associatedtype ResponseType: Any
}

protocol MappableProtocol {
    init(map: String)
}

我正在添加我的数据类MyDto

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

和3个不同的响应类

class A1: ResponseProtocol {
    typealias ResponseType = String
}

class A2: ResponseProtocol {
    typealias ResponseType = MyDto
}

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

现在我需要根据ResponseType做不同的事情。

我尝试了这段代码,但我遇到了Array s

的问题
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())

在我的游乐场控制台中,对于数组A3,我看到Array<MyDto>,但我期望第一次返回数组Array<Any>

如何检查Array属于Element的{​​{1}}?

1 个答案:

答案 0 :(得分:2)

问题是虽然[MyDto]实例可以自由转换为[MappableProtocol][Any],但这些实际上只是编译器所做的神奇转换场景(更多信息见this Q&A)。

同样的转换对于元类型值并不存在,这就是为什么Swift说[MyDto].Type不是[MappableProtocol].Type也不是[Any].Type - 它们是不相关的元类型类型。

在您的情况下,最简单的解决方案可能就是忘记使用元类型,而只是声明test(a:)的不同重载来处理不同的ResponseType类型。

// '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>

(为了简化事情,我删除了您的API课程)

编译器将简单地解决在编译时调用哪个重载,而不是让运行时跳过大量的类型转换箍。

如果坚持使用元类型值,一种可能的解决方案是为Array定义虚拟协议以符合(参见例如{{ 3}}),然后我们可以将元类型值转换为。然后,我们可以声明elementType静态要求,以便提取Array的{​​{1}}元类型值,然后我们可以检查其类型以确定数组是什么可转换为。

例如,如果我们定义Element.self并将Array定义为_ArrayProtocol

protocol _ArrayProtocol {
    static var elementType: Any.Type { get }
}

extension Array : _ArrayProtocol {
    static var elementType: Any.Type {
        return Element.self
    }
}

我们现在可以像这样使用test(a:)

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>