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>,响应体自动解析为任意模型
相关推荐
东坡肘子1 天前
毕业 30 年同学群:一场 AI 引发的“真假难辨”危机 -- 肘子的 Swift 周报 #112
人工智能·swiftui·swift
Antonio9152 天前
【Swift】UIKit:UIAlertController、UIImageView、UIDatePicker、UIPickerView和UISwitch
ios·cocoa·swift
Antonio9152 天前
【Swift】UIKit:UISegmentedControl、UISlider、UIStepper、UITableView和UICollectionView
开发语言·ios·swift
1***81532 天前
Swift在服务端开发的可能性探索
开发语言·ios·swift
S***H2832 天前
Swift在系统级应用中的开发
开发语言·ios·swift
HarderCoder2 天前
SwiftUI 状态管理极简之道:从“最小状态”到“状态树”
swift
Antonio9152 天前
【Swift】 UIKit:UIGestureRecognizer和UIView Animation
开发语言·ios·swift
蒙小萌19933 天前
Swift UIKit MVVM + RxSwift Development Rules
开发语言·prompt·swift·rxswift
Antonio9153 天前
【Swift】Swift基础语法:函数、闭包、枚举、结构体、类与属性
开发语言·swift
Antonio9153 天前
【Swift】 Swift 基础语法:变量、类型、分支与循环
开发语言·swift