可以在Swift中创建泛型类型检查功能吗?

时间:2016-07-11 17:38:55

标签: swift generics introspection

我正在为我继承的Web API框架添加更好的错误处理。目前它会进行一系列强制转换,当数据与预期的数据不匹配时会导致崩溃。

我想用一个检查类型的函数替换as!的所有用法,并在失败时引发异常(带有详细描述)。

这是我到目前为止所做的:

func checkType<T>(type: AnyClass, value: T?, name: String) throws -> T {
    guard let value = value else {
        let message = "[\(name)] Expected \(type), but value was nil"
        throw PlaidsterError.InvalidType(message)
    }

    guard value.dynamicType.self == type else {
        let message = "[\(name)] Expected \(type), but it was an \(value.dynamicType.self) with value: \(value)"
        throw PlaidsterError.InvalidType(message)
    }

    return value
}

但这有很多问题。它只能接受对象类型,如果类不完全匹配则失败(例如NSString因为它实际上是__NSCFString而失败),并且它不能用于任何类型,即String,Int,Double ......

我似乎无法为AnyClass值类型找到Any的同义词,这是第一个问题。此外,它似乎无法执行value.dynamicType.self is type之类的操作,因为它表示type不是类型。

我可以做我想做的事吗?有没有更好的方法来进行这种类型检查,而没有遍布解析代码的大量样板代码?

我的目标是得到类似的东西,甚至更简单:

public struct PlaidCategory {

    // MARK: Properties
    public let id: String
    public let hierarchy: [String]
    public let type: String

    // MARK: Initialization
    public init(category: [String: Any]) throws {
        id = try checkType(String.self, value: category["id"], name: "id")
        hierarchy = try checkType([String].self, value: category["hierarchy"], name: "hierarchy")
        type = try checkType(String.self, value: category["type"], name: "type")
}

}

1 个答案:

答案 0 :(得分:3)

您可以使用另一个泛型类型参数编写类似的内容。

func checkType<T, U>(type: U.Type, value: T?, name: String) throws -> U {
    guard let value = value else {
        let message = "[\(name)] Expected \(type), but value was nil"
        throw PlaidsterError.InvalidType(message)
    }

    guard let result = value as? U else {
        let message = "[\(name)] Expected \(type), but it was an \(value.dynamicType) with value: \(value)"
        throw PlaidsterError.InvalidType(message)
    }

    return result
}

并将其用作:

do {
    let n: Any = 3
    let t = try checkType(Int.self, value: n, name: "n")
    print(t) //->3
} catch let error {
    print(error)
}
do {
    let n: Any = "x"
    let t = try checkType(Int.self, value: n, name: "n")
    print(t)
} catch let error {
    print(error) //->InvalidType("[n] Expected Int, but it was an _NSContiguousString with value: x")
}

我希望这适用于您的PlaidCategory.init(category:),但未经过测试。

还有一个。如果仅在返回类型可推断的情况下使用它,则无需传递type参数。

func checkType<T, U>(value: T?, name: String) throws -> U {
    guard let value = value else {
        let message = "[\(name)] Expected \(U.self), but value was nil"
        throw PlaidsterError.InvalidType(message)
    }

    guard let result = value as? U else {
        let message = "[\(name)] Expected \(U.self), but it was an \(value.dynamicType) with value: \(value)"
        throw PlaidsterError.InvalidType(message)
    }

    return result
}

并将其用作:

do {
    let n: Any = "x"
    let t: Int = try checkType(n, name: "n")
    print(t)
} catch let error {
    print(error) //->InvalidType("[n] Expected Int, but it was an String with value: x")
}

或:

public struct PlaidCategory {

    // MARK: Properties
    public let id: String
    public let hierarchy: [String]
    public let type: String

    // MARK: Initialization
    public init(category: [String: Any]) throws {
        id = try checkType(category["id"], name: "id")
        hierarchy = try checkType(category["hierarchy"], name: "hierarchy")
        type = try checkType(category["type"], name: "type")
    }
}