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>,响应体自动解析为任意模型
相关推荐
胖虎16 小时前
SwiftUI 页面作为一级页面数据被重置问题分析
ios·swiftui·swift·state·observedobject·stateobject·swiftui页面生命周期
健了个平_249 小时前
【iOS】如何在 iOS 26 的UITabBarController中使用自定义TabBar
ios·swift·wwdc
1024小神15 小时前
xcode 配置了AppIcon 但是不显示icon图标
ios·swiftui·swift
奶糖 肥晨15 小时前
架构深度解析|基于亚马逊云科技与Swift Alliance Cloud构建高可用金融报文交换架构
科技·架构·swift
Swift社区2 天前
用 Task Local Values 构建 Swift 里的依赖容器:一种更轻量的依赖注入思路
开发语言·ios·swift
TouchWorld2 天前
iOS逆向-哔哩哔哩增加3倍速播放(4)- 竖屏视频·全屏播放场景
ios·swift
1024小神2 天前
swift中使用ObservableObject单利模式的时候,用let 或 @ObservedObject 或 @StateObject 有什么区别
开发语言·ios·swift
1024小神2 天前
swift中 列表、字典、集合、元祖 常用的方法
数据结构·算法·swift
TouchWorld3 天前
iOS逆向-哔哩哔哩增加3倍速播放(3)-[横屏视频-全屏播放]场景
ios·swift