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

Possible to make a generic type checking function in Swift?

我正在为我继承的 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
}

但这有多个问题。它只能接受对象类型,如果 class 不完全匹配则失败(例如 NSString 失败,因为它实际上是 __NSCFString),并且它不能与任何类型一起工作,即 String、Int , 双...

我似乎无法为 Any 值类型找到 AnyClass 的同义词,这是第一个问题。此外,似乎无法执行 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")
}

}

你可以用另一个泛型类型参数来写这样的东西。

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:),但未经测试。


再来一个。如果仅在 return 类型可推断的情况下使用它,则无需传递 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")
    }
}