Swift类型推断
hudson 译 原文
Swift 是一种静态类型语言,这意味着我们声明的每个属性、常量和变量的类型都需要在编译时指定。然而,通常情况下,这不是必须手动完成的事情,相反,编译器能够根据被分配的值自动推断出各种类型信息 --- 这得益于 Swift 支持类型推断的特性。
因此,例如,这里我们声明了几个常量 ------ 完全没有指定任何类型,因为编译器能够根据被分配的值推断出信息:
swift
let number = 42
let string = "Hello, world!"
let array = [1, 1, 2, 3, 5, 8]
let dictionary = ["key": "value"]
为了对比,以下是如果我们手动指定每个常量的类型时的情况:
swift
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
值数组:
swift
enum ContactKind {
case family
case friend
case coworker
case acquaintance
}
func loadContacts(ofKind kind: ContactKind) -> [Contact] {
...
}
虽然通常会通过同时指定类型和成员(例如 ContactKind.friend
)来引用上述枚举的成员,但由于类型推断,当在已知类型的上下文中引用枚举成员时,可以完全省略类型的名称 ------ 就像在调用上述函数时那样:
swift
let friends = loadContacts(ofKind: .friend)
真正酷的是,上述的"点语法"不仅适用于枚举成员,在引用任何静态属性或方法时也是如此。例如,这里我们对Foundation 的URL
类进行扩展,增加了一个静态属性 ,用于创建一个指向这个网站的 URL
:
swift
extension URL {
static var swiftBySundell: URL {
URL(string: "https://swiftbysundell.com")!
}
}
现在,当调用任何接受URL
参数的方法时(例如新的 Combine框架增强的 URLSession
API),我们可以简单地引用上述属性:
swift
let publisher = URLSession.shared.dataTaskPublisher(for: .swiftBySundell)
非常棒!然而,虽然类型推断是一种非常有用的特性,但仍然存在一些情况,可能需要额外指定一些类型信息以实现想要的结果。
这些情况中,一个非常常见例子是处理数值类型。当将数值文字分配给变量或常量时,它默认会被推断为 Int
类型 ------ 这是一个完全合理的默认值 ------ 但如果希望使用其他数值类型,比如 Double
或 Float
,就需要手动指定这些类型。以下是几种方法:
swift
let int = 42
let double = 42 as Double
let float: Float = 42
let cgFloat = CGFloat(42)
还有一种情况是,在调用具有泛型返回类型的函数时,也可能需要给编译器提供额外的类型信息 。
例如,这里我们扩展了内置的 Bundle
类, 增加一个泛型方法,该方法能够轻松加载和解码应用程序中捆绑的任何 JSON 文件:
swift
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 内置的错误处理机制(上面代码中,通过使用
throws
和try
关键字 )的信息,请查阅关于错误处理的基础文章 。
现在假设在应用程序开发中,在真实服务器和网络代码准备就绪之前,我们希望从捆绑的 JSON 文件中解码以下 User
类型的实例:
swift
struct User: Codable {
var name: String
var email: String
var lastLoginDate: Date
}
然而,如果像这样调用decodeJSONFile
方法,将会得到一个编译器错误:
swift
// 错误:无法推断泛型参数 'T'
let user = try Bundle.main.decodeJSONFile(named: "user-mock")
这是因为即将解码的任何给定的 JSON 文件的确切类型取决于泛型类型 T 在每个调用点上实际引用的内容 ------ 而由于我们在上面没有给编译器提供任何此类信息,所以将得到一个错误。在这种情况下,编译器无法知道我们希望解码那种类型User
实例。
要解决这个问题,可以使用与上面用于指定不同类型数值的技术相同的技术,要么给我们的 user
常量一个明确的类型,要么使用 as
关键字 ------ 就像这样:
swift
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
类型的属性,我们将结果赋值给这个属性:
swift
struct MockData {
var user: User
}
let mockData = try MockData(
user: Bundle.main.decodeJSONFile(named: "user-mock")
)
这就是对 Swift 类型推断能力的简要介绍。值得指出的是,类型推断确实有与之相关的计算成本,但幸运的是,这些成本完全发生在编译时(因此不会影响应用程序的运行时性能),但在处理更复杂的表达式时,仍然值得注意。如果遇到一个需要编译器很长时间才能弄清楚的表达式,那么我们总是可以使用上面的任何一种技术来手动指定这些类型。
谢谢阅读! 🚀