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

谢谢阅读! 🚀

相关推荐
良技漫谈2 天前
Rust移动开发:Rust在iOS端集成使用介绍
后端·程序人生·ios·rust·objective-c·swift
KeithTsui3 天前
ZFC in LEAN 之 前集的等价关系(Equivalence on Pre-set)详解
开发语言·其他·算法·binder·swift
袁代码3 天前
Swift 开发教程系列 - 第4章:函数与闭包
ios·swift·ios开发
安泽13144 天前
高德地图美食
开发语言·swift·美食
袁代码4 天前
Swift 开发教程系列 - 第2章:Swift 基础语法
swift·ios开发·基础教程
袁代码4 天前
Swift 开发教程系列 - 第1章:Swift 简介与开发环境配置
swift·ios开发·基础教程
孚亭5 天前
一些swift问题
swift
莫问alicia5 天前
echarts 实现3D饼状图 加 label标签显示
前端·3d·echarts·swift
uiop_uiop_uiop7 天前
iOS Swift5算法恢复——HMAC
ios·iphone·swift
東三城8 天前
【ios】---SwiftUI开发从入门到放弃
ios·swiftui·swift·1024程序员节