Swift类型揣度

hudson 译 原文

Swift 是一种静态类型言语,这意味着咱们声明的每个特点、常量和变量的类型都需求在编译时指定。然而,通常状况下,这不是有必要手动完成的工作,相反,编译器能够依据被分配的值主动揣度出各种类型信息 — 这得益于 Swift 支撑类型揣度的特性。

因而,例如,这儿咱们声明了几个常量 —— 彻底没有指定任何类型,由于编译器能够依据被分配的值揣度出信息:

let number = 42
let string =Hello, world!let array = [1, 1, 2, 3, 5, 8]
let dictionary = [“key”: “value”]

为了比照,以下是假如咱们手动指定每个常量的类型时的状况:

let number: Int = 42
let string: String =Hello, world!let array: [Int] = [1, 1, 2, 3, 5, 8]
let dictionary: [String: String] = [“key”: “value”]

因而,为使Swift 语法尽或许轻便,类型揣度起着重要作用,类型揣度不只适用于变量声明和其他类型的赋值句子,而且在许多其他类型的状况下也是如此。

例如,这儿咱们定义了一个枚举,描述了各种联系人类型,以及一个函数,能够加载属于特定类型的Contact数组

enum ContactKind {
    case family
    case friend
    case coworker
    case acquaintance
}
func loadContacts(ofKind kind: ContactKind) -> [Contact] {
    ...
}

虽然通常会经过一起指定类型和成员(例如 ContactKind.friend)来引证上述枚举的成员,但由于类型揣度,当在已知类型的上下文中引证枚举成员时,能够彻底省略类型的名称 —— 就像在调用上述函数时那样:

let friends = loadContacts(ofKind: .friend)

真实酷的是,上述的“点语法”不只适用于枚举成员,在引证任何静态特点或办法时也是如此。例如,这儿咱们对Foundation 的URL 类进行扩展,添加了一个静态特点 ,用于创立一个指向这个网站的 URL

extension URL {
    static var swiftBySundell: URL {
        URL(string: “https://swiftbysundell.com”)!
    }
}

现在,当调用任何承受URL 参数的办法时(例如新的 Combine结构增强的 URLSessionAPI),咱们能够简单地引证上述特点:

let publisher = URLSession.shared.dataTaskPublisher(for: .swiftBySundell)

十分棒!然而,虽然类型揣度是一种十分有用的特性,但依然存在一些状况,或许需求额定指定一些类型信息以实现想要的成果。

这些状况中,一个十分常见比如是处理数值类型。当将数值文字分配给变量或常量时,它默认会被揣度为 Int 类型 —— 这是一个彻底合理的默认值 —— 但假如期望运用其他数值类型,比如 Double Float,就需求手动指定这些类型。以下是几种办法:

let int = 42
let double = 42 as Double
let float: Float = 42
let cgFloat = CGFloat(42)

还有一种状况是,在调用具有泛型回来类型的函数时,也或许需求给编译器提供额定的类型信息 。

例如,这儿咱们扩展了内置的 Bundle 类,
添加一个泛型办法,该办法能够轻松加载和解码应用程序中绑缚的任何 JSON 文件:

extension Bundle {
    struct MissingFileError: Error {
        var name: String
    }
    func decodeJSONFile<T: Decodable>(named name: String) throws -> T {
        guard let url = self.url(forResource: name, withExtension: “json”) else {
            throw MissingFileError(name: name)
        }
        let data = try Data(contentsOf: url)
        let decoder = JSONDecoder()
        return try decoder.decode(T.self, from: data)
    }
}

要了解更多关于 Swift 内置的过错处理机制(上面代码中,经过运用throwstry 关键字 )的信息,请查阅关于过错处理的基础文章

现在假设在应用程序开发中,在真实服务器和网络代码准备就绪之前,咱们期望从绑缚的 JSON 文件中解码以下 User 类型的实例:

struct User: Codable {
    var name: String
    var email: String
    var lastLoginDate: Date
}

然而,假如像这样调用decodeJSONFile 办法,将会得到一个编译器过错:

// 过错:无法揣度泛型参数 ‘T’
let user = try Bundle.main.decodeJSONFile(named: “user-mock”)

这是由于行将解码的任何给定的 JSON 文件的确切类型取决于泛型类型 T 在每个调用点上实际引证的内容 —— 而由于咱们在上面没有给编译器提供任何此类信息,所以将得到一个过错。在这种状况下,编译器无法知道咱们期望解码那种类型User 实例。

要解决这个问题,能够运用与上面用于指定不同类型数值的技能相同的技能,要么给咱们的 user 常量一个清晰的类型,要么运用 as 关键字 —— 就像这样:

let user: User = try Bundle.main.decodeJSONFile(named: “user-mock”)
let user = try Bundle.main.decodeJSONFile(named: “user-mock”) as User

然而,假如在一个已知期望的回来类型的上下文中调用decodeJSONFile 办法,那么能够再次让 Swift 的类型揣度机制找到该信息 —— 就像在下面这种状况下那样,咱们定义了一个名为 MockData 的包装器结构,该结构具有一个 User 类型的特点,咱们将成果赋值给这个特点:

struct MockData {
    var user: User
}
let mockData = try MockData(
    user: Bundle.main.decodeJSONFile(named: “user-mock”)
)

这就是对 Swift 类型揣度才能的简要介绍。值得指出的是,类型揣度确实有与之相关的计算本钱,但走运的是,这些本钱彻底产生在编译时(因而不会影响应用程序的运行时功能),但在处理更杂乱的表达式时,依然值得注意。假如遇到一个需求编译器很长时刻才能弄清楚的表达式,那么咱们总是能够运用上面的任何一种技能来手动指定这些类型。

谢谢阅读!