Swift 泛型深度指南 ——从“交换两个值”到“通用容器”的代码复用之路

为什么需要泛型

  1. 无泛型时代的"粘贴式"编程
swift 复制代码
// 只能交换 Int
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temp = a
    a = b
    b = temp
}

// 复制粘贴,改个类型名
func swapTwoStrings(_ a: inout String, _ b: inout String) {
    let temp = a
    a = b
    b = temp
}

问题:

  • 代码完全一样,仅类型不同
  • 维护 N 份副本,牵一发而动全身

泛型函数:写一次,跑所有类型

  1. 语法与占位符 T
swift 复制代码
// <T> 声明一个"占位符类型"
// 编译器每次调用时把 T 替换成真实类型
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}
  1. 调用方式:编译器自动推断 T
swift 复制代码
var x = 3, y = 5
swapTwoValues(&x, &y)          // T 被推断为 Int

var s1 = "A", s2 = "B"
swapTwoValues(&s1, &s2)        // T 被推断为 String

要点:

  • 占位符名可以是任意合法标识符,习惯单字母 TUV
  • 同一函数签名里所有 T 必须是同一真实类型,不允许 swapTwoValues(3, "hello")

泛型类型:自定义可复用容器

  1. 非泛型版 Int 栈
swift 复制代码
struct IntStack {
    private var items: [Int] = []
    mutating func push(_ item: Int) { items.append(item) }
    mutating func pop() -> Int { items.removeLast() }
}
  1. 泛型版 Stack
swift 复制代码
struct Stack<Element> {               // Element 为占位符
    private var items: [Element] = []
    mutating func push(_ item: Element) { items.append(item) }
    mutating func pop() -> Element { items.removeLast() }
}

// 用法
var intStack = Stack<Int>()
intStack.push(1)
intStack.push(2)
print(intStack.pop())   // 2

var strStack = Stack<String>()
strStack.push("🍎")
strStack.push("🍌")
print(strStack.pop())   // 🍌

类型约束:给"任意类型"划边界

  1. 场景:在容器里查找元素索引
swift 复制代码
// 编译失败版:并非所有 T 都支持 ==
func findIndex<T>(of valueToFind: T, in array: [T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {          // ❌ 错误:T 不一定能比较
            return index
        }
    }
    return nil
}
  1. 加入 Equatable 约束
swift 复制代码
func findIndex<T: Equatable>(of valueToFind: T, in array: [T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {          // ✅ 现在合法
            return index
        }
    }
    return nil
}

小结:

  • 语法 <T: SomeProtocol><T: SomeClass>
  • 可同时约束多个占位符:<T: Equatable, U: Hashable>

关联类型(associatedtype):协议里的"泛型"

  1. 定义容器协议
swift 复制代码
protocol Container {
    associatedtype Item                // 占位名
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(index: Int) -> Item { get }
}
  1. 让泛型 Stack 直接 conform
swift 复制代码
extension Stack: Container {
    // 编译器自动推断:
    // append 参数 item 类型 = Element
    // subscript 返回值类型 = Element
    // 故 Item == Element,无需手写 typealias
}
  1. 给关联类型加约束
swift 复制代码
protocol EquatableContainer: Container where Item: Equatable { }
// 现在所有 Item 必须支持 ==,可直接在扩展里使用 ==/!=

泛型 where 子句:更细粒度的约束

  1. 函数级 where
swift 复制代码
// 检查两个容器是否完全一致(顺序、元素、数量)
func allItemsMatch<C1: Container, C2: Container>
    (_ someContainer: C1, _ anotherContainer: C2) -> Bool
    where C1.Item == C2.Item, C1.Item: Equatable
{
    if someContainer.count != anotherContainer.count { return false }
    for i in 0..<someContainer.count {
        if someContainer[i] != anotherContainer[i] { return false }
    }
    return true
}
  1. 扩展级 where
swift 复制代码
extension Stack where Element: Equatable {
    // 仅当元素可比较时才出现该方法
    func isTop(_ item: Element) -> Bool {
        guard let top = items.last else { return false }
        return top == item
    }
}
  1. 下标也能泛型 + where
swift 复制代码
extension Container {
    // 接收一组索引,返回对应元素数组
    subscript<Indices: Sequence>(indices: Indices) -> [Item]
        where Indices.Element == Int
    {
        var result: [Item] = []
        for index in indices {
            result.append(self[index])
        }
        return result
    }
}

隐式约束与 Copyable(Swift 5.9+)

Swift 自动为泛型参数加上 Copyable 约束,以便值能被多次使用。

若你明确允许"可复制或不可复制",用前缀 ~ 抑制隐式约束:

swift 复制代码
func consumeOnce<T>(_ x: consuming T) where T: ~Copyable {
    // x 只能被移动一次,不能再复制
}

示例代码

把下面代码一次性粘进 Playground,逐行跑通:

swift 复制代码
import Foundation

// 1. 通用交换
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
    let t = a; a = b; b = t
}

// 2. 泛型栈
struct Stack<Element> {
    private var items: [Element] = []
    mutating func push(_ e: Element) { items.append(e) }
    mutating func pop() -> Element { items.removeLast() }
}
extension Stack: Container {
    typealias Item = Element
    mutating func append(_ item: Element) { push(item) }
    var count: Int { items.count }
    subscript(i: Int) -> Element { items[i] }
}

// 3. 协议
protocol Container {
    associatedtype Item
    mutating func append(_ item: Item)
    var count: Int { get }
    subscript(index: Int) -> Item { get }
}

// 4. 扩展约束
extension Stack where Element: Equatable {
    func isTop(_ e: Element) -> Bool { items.last == e }
}

// 5. 测试
var s = Stack<Int>()
s.push(10)
s.push(20)
print(s.isTop(20))        // true
print(s.pop())            // 20

var a: [String] = ["A","B","C"]
var b: Array<String> = ["A","B","C"]
print(allItemsMatch(a, b)) // true

总结与实战扩展

  1. 泛型 = 代码的"模板引擎"

    把"类型"也当成参数,一次性书写,多处复用,减少 BUG

  2. 约束是"接口隔离"的利器

    与其写长篇文档说明"请传能比较的值",不如让编译器帮你拦下来:<T: Equatable>

  3. 关联类型让协议"带泛型"

    协议不再只能定义"行为",还能定义"元素类型",使协议与泛型容器无缝衔接。

  4. where 子句是"需求说明书"

    函数、扩展、下标都能写 where,把"调用前提"写进类型系统,用不合法的状态直接编译不过,比运行时断言更早暴露问题。

  5. 实战场景举例

    • JSON 解析层:写一个 Decoder<T: Decodable>,一份代码支持所有模型

    -缓存框架:定义 Cache<Key: Hashable, Value>,Key 约束为 Hashable,Value 任意类型

    • 网络请求:Request<Resp: Decodable>,响应体自动解析为任意模型
相关推荐
HarderCoder7 小时前
Swift 中的不透明类型与装箱协议类型:概念、区别与实践
swift
东坡肘子8 小时前
惊险但幸运,两次!| 肘子的 Swift 周报 #0109
人工智能·swiftui·swift
胖虎18 小时前
Swift项目生成Framework流程以及与OC的区别
framework·swift·1024程序员节·swift framework
songgeb1 天前
What Auto Layout Doesn’t Allow
swift
YGGP1 天前
【Swift】LeetCode 240.搜索二维矩阵 II
swift
YGGP2 天前
【Swift】LeetCode 73. 矩阵置零
swift
非专业程序员Ping3 天前
HarfBuzz 实战:五大核心API 实例详解【附iOS/Swift实战示例】
android·ios·swift
Swift社区4 天前
LeetCode 409 - 最长回文串 | Swift 实战题解
算法·leetcode·swift
YGGP6 天前
【Swift】LeetCode 54. 螺旋矩阵
swift