Swift 中的泛型

Swift 泛型

hudson 译 原文

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

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

泛型使得同一种类型、协议或函数能够针对大量用例进行特化。例如,由于 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
}

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

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

请注意,在上面不需要指定要将 Container特定的哪些具体类型 ------ Swift 的类型推断会自动推断出stringContainerContainer<String> 实例,而 intContainerContainer<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 表示 UUIDTag.ID 表示 Int ------ 现在,Article Tag 都可以传递给接受符合 Identifiable 的值的任何函数,同时仍然保持不同的类型,甚至使用自己的不同类型的标识符。

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

感谢阅读!🚀

相关推荐
Keya20 小时前
lipo 命令行指南
ios·xcode·swift
zhangmeng20 小时前
SwiftUI中如何实现子视图向父视图传递数据?
ios·swiftui·swift
Saafo20 小时前
迁移至 Swift Actors
ios·swift
杂雾无尘2 天前
告别构建错误, iOS 开发架构难题全面解析, 避免 CPU 架构陷阱
ios·swift·客户端
大熊猫侯佩3 天前
探秘 WWDC 25 全新 #Playground 宏:提升 Swift 开发效率的超级神器
xcode·swift·wwdc
移动端小伙伴3 天前
10.推送的扩展能力 — 打造安全的通知体验
swift
移动端小伙伴3 天前
推送的扩展能力 — 打造个性化的通知体验
swift
移动端小伙伴3 天前
远程推送(Remote Push Notification)
swift
移动端小伙伴3 天前
本地通知的精准控制三角:时间、位置、情境
swift
移动端小伙伴3 天前
本地通知内容深度解析 — 打造丰富的通知体验
swift