类型推断

木木木大约 4 分钟iOSSwift

类型推断

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 googleUrl: URL {
        URL(string: "https://google.com")!
    }
}

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)
    }
}

现在,假设在应用程序开发过程中,在真正的服务器和网络代码启动并运行之前,我们希望从捆绑的JSON文件中解码以下User类型的实例:

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

然而,如果像这样调用decodeJSONFile方法,最终会出现编译器错误:

// Error: Generic parameter 'T' could not be inferred
let user = try Bundle.main.decodeJSONFile(named: "user-mock")

这是因为将解码任何给定JSON文件的确切类型取决于通用类型T在每个调用站点的实际引用什么——由于没有向编译器提供上述任何此类信息,最终会出现错误。在这种情况下,编译器根本无法知道希望解码User实例。 为了解决这个问题,可以使用与上面相同的技术来指定不同类型的数值,并给user常量一个显式类型,或者使用关键字:

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")
)

类型推断确实有与之相关的计算成本,这完全发生在编译时,因此它不会影响我们应用程序的运行时性能。然而,如果我们确实遇到一个表达式编译器需要很长时间才能弄清楚,那么总是可以使用上述技术之一来手动指定这些类型。

上次编辑于:
贡献者: perhapsdone