Swift类型推断

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框架增强的 URLSessionAPI),我们可以简单地引用上述属性:

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 内置的错误处理机制(上面代码中,通过使用throwstry 关键字 )的信息,请查阅关于错误处理的基础文章

现在假设在应用程序开发中,在真实服务器和网络代码准备就绪之前,我们希望从捆绑的 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 类型推断能力的简要介绍。值得指出的是,类型推断确实有与之相关的计算成本,但幸运的是,这些成本完全发生在编译时(因此不会影响应用程序的运行时性能),但在处理更复杂的表达式时,仍然值得注意。如果遇到一个需要编译器很长时间才能弄清楚的表达式,那么我们总是可以使用上面的任何一种技术来手动指定这些类型。

谢谢阅读! 🚀

相关推荐
Swift社区17 小时前
Apple 新品发布会亮点有哪些 | Swift 周报 issue 61
ios·swiftui·swift
营赢盈英3 天前
OpenAI GPT-3 API error: “You must provide a model parameter“
chatgpt·gpt-3·openai·swift
一只不会编程的猫4 天前
高德地图绘图,点标记,并计算中心点
开发语言·ios·swift
loongloongz4 天前
Swift语言基础教程、Swift练手小项目、Swift知识点实例化学习
开发语言·学习·swift
2401_858120538 天前
深入理解 Swift 中的隐式解包可选类型(Implicitly Unwrapped Optionals)
开发语言·ios·swift
quaer8 天前
QT chart案例
开发语言·qt·swift
安和昂8 天前
【iOS】UIViewController的生命周期
ios·xcode·swift
00圈圈8 天前
Swift 创建扩展(Extension)
ios·swift·extension
2401_858120538 天前
Swift 中的函数:定义、使用与实践指南
开发语言·ssh·swift
quaer9 天前
VS+QT--实现二进制和十进制的转换(含分数部分)
java·开发语言·python·qt·swift