Swift 中的泛型

Swift 泛型

hudson 译 原文

Swift 允许我们创建不与任何特定具体类型绑定的泛型类型、协议和函数,与满足一组给定要求的任何类型一起使用。

作为一种强类型安全的语言,泛型是 Swift 的一个核心特性,对于 Swift 的许多方面都非常重要 ------ 包括其标准库,后者大量使用了泛型。只需看看一些基本数据结构,比如 Array 和 Dictionary,这两者都是泛型的。

泛型使得同一种类型、协议或函数能够针对大量用例进行特化。例如,由于 Array 是泛型的,它允许为任何类型创建专门的实例 ------ 比如字符串:

swift 复制代码
var array = ["One", "Two", "Three"]
array.append("Four")

// 由于上面的数组是专门针对字符串的,因此不能插入其他类型的值:
array.append(5)

// 当我们从数组中取出一个元素时,我们仍然可以像正常的字符串一样对待它,因为我们具有完整的类型安全性。
let characterCount = array[0].count

要创建自己的泛型,只需定义泛型类型是什么,以及可选的附加约束。例如, 下面创建一个 Container 类型,它可以包含任何值,以及一个日期:

swift 复制代码
struct Container<Value> {
    var value: Value
    var date: Date
}

就像能够创建特定的Array和Dictionary一样,我们也可以为任何类型的值创建特定Container,比如字符串或整数:

swift 复制代码
let stringContainer = Container(value: "Message", date: Date())
let intContainer = Container(value: 7, date: Date())

请注意,在上面不需要指定要将 Container特定的哪些具体类型 ------ Swift 的类型推断会自动推断出stringContainer 是 Container<String> 实例,而 intContainer 是Container<Int> 实例。

当编写可能适用于许多不同类型的代码时,泛型尤其有用。例如,我们可能会使用上面的 Container 来实现一个泛型的 Cache,它可以为任何类型的键存储任何类型的值。在这种情况下,还需要添加了一个约束:Key 需要遵循Hashable,以便可以将其用于字典 ------ 就像这样:

swift 复制代码
class Cache<Key: Hashable, Value> {
    private var values = [Key: Container<Value>]()

    func insert(_ value: Value, forKey key: Key) {
        let expirationDate = Date().addingTimeInterval(1000)

        values[key] = Container(
            value: value,
            date: expirationDate
        )
    }

    func value(forKey key: Key) -> Value? {
        guard let container = values[key] else {
            return nil
        }

        // 如果容器的日期是过去的,那么值已经过期,我们将其从缓存中删除。
        guard container.date > Date() else {
            values[key] = nil
            return nil
        }

        return container.value
    }
}

有了以上内容,现在可以为任何类型创建类型安全的缓存 ------ 例如用户或搜索结果:

swift 复制代码
class UserManager {
    private var cachedUsers = Cache<User.ID, User>()
    ...
}

class SearchController {
    private var cachedResults = Cache<Query, [SearchResult]>() 
    ...
}

上面的代码中,我们确实需要指定 Cache 具体化的类型,因为编译器无法从调用点推断出该信息。

单个函数无论在何处定义也可以是泛型的。例如,下面代码扩展 String(它不是泛型类型),以添加一个泛型函数,该函数允许轻松地增加数组中所有 Identifiable 值的 ID:

swift 复制代码
extension String {
    mutating func appendIDs<T: Identifiable>(of values: [T]) {
        for value in values {
            append(" \(value.id)")
        }
    }
}

甚至协议也可以是泛型的!实际上,上面的 Identifiable 协议就是一个例子,因为它使用关联类型来使其能够用任何类型的 ID 类型进行具体化------ 就像这样:

swift 复制代码
protocol Identifiable {
    associatedtype ID: Equatable & CustomStringConvertible

    var id: ID { get }
}

上面的方法使得每个符合 Identifiable 协议的类型都可以决定它想要使用的 ID 类型 ------ 同时仍然能够充分利用我们为 Identifiable 类型编写的所有泛型代码(比如上面的 String 扩展)。

例如,这里是 Article 类型使用 UUID 值作为 ID,而 Tag 类型可能只是使用整数(作为ID):

swift 复制代码
struct Article: Identifiable {
    let id: UUID
    var title: String
    var body: String
}

struct Tag: Identifiable {
    let id: Int
    var name: String
}

上面的技术非常有用。例如与另一个系统(如服务器端后端)兼容时,需要某些数据模型使用特定类型的 ID 。

再次,上述代码中,编译器将完成大部分繁重的工作,因为它将根据每个类型的 id 属性自动推断出 Article.ID 表示 UUID,Tag.ID 表示 Int ------ 现在,Article 和 Tag 都可以传递给接受符合 Identifiable 的值的任何函数,同时仍然保持不同的类型,甚至使用自己的不同类型的标识符。

这就是泛型的强大之处,它使我们能够编写更容易重用的代码,同时仍然能够进行局部具体化。算法、数据结构和实用工具通常是泛型的最佳候选 ------ 因为它们通常只需要其使用类型满足一定的要求,而不是与特定的具体类型绑定。

感谢阅读!🚀

相关推荐
茶底世界之下2 天前
时间线效果层,为什么不能只复用一个处理器?
ios·swift
Cc.Y2 天前
Swift深度链接的极简之道
ios·cocoa·swift
传奇开心果编程2 天前
【SwiftUI提高练中学】第9课 安全与隐私
swiftui·移动开发·swift·编程语言理论·开发范式·编程框架·声明式ui
茶底世界之下3 天前
动态切换 nearest / linear 时,为什么不能只替换 sampler
ios·swift
应用市场3 天前
把旧 Pixel 变成相册备份中转站(上):Mac 到安卓的照片传输工具设计——流式上传、sha256 校验、adb forward 与 Bonjour
android·macos·adb·kotlin·swift
东坡肘子3 天前
Swift Server,又多了一个 Google -- 肘子的 Swift 周报 #156
人工智能·swiftui·swift
打工仔折腾 AI4 天前
从零写一个CAD 05:以鼠标为中心的滚轮缩放,招法能复用但顺序不能反
开发语言·后端·python·性能优化·计算机外设·swift·ai agent 实战
茶底世界之下4 天前
视频预览切换为何会闪回旧帧:用 generation + mode identity 管住异步回调
ios·swift
茶底世界之下4 天前
类型擦除之后,Metal 滤镜组合为什么不能只执行一个 Filter?
ios·swift