前言
Swift 提供了两种隐藏类型信息的方式:不透明类型(opaque type) 和 装箱协议类型(boxed protocol type)。
它们都用于隐藏具体类型,但在类型身份、性能、灵活性等方面有本质区别。
不透明类型(Opaque Types)
基本概念
不透明类型允许函数返回一个遵循某个协议的具体类型,但调用者无法知道具体是什么类型。编译器知道类型信息,但调用者不知道。
使用关键字 some 来声明不透明类型。
            
            
              swift
              
              
            
          
          protocol Shape {
    func draw() -> String
}
struct Square: Shape {
    func draw() -> String {
        return "■"
    }
}
func makeSquare() -> some Shape {
    return Square()
}
        调用者知道返回的是一个 Shape,但不知道它是 Square。
与泛型的区别
泛型是调用者决定类型,不透明类型是实现者决定类型。
            
            
              swift
              
              
            
          
          // 泛型:调用者决定类型
func maxNum<T: Comparable>(_ x: T, _ y: T) -> T {
    // 实现者,不知道T是什么类型,只知道T可以进行比较
    return x > y ? x : y
}
// 调用者传入1,3。编译器会自动推断T为Int
// 也可以补全写法 maxNum<Int>(1, 3)
let maxInt = maxNum(1,3)
// 不透明类型:实现者决定类型
func makeShape() -> some Shape {
    // 实现者 决定最终返回的具体类型
    return Square()
}
// 下面的写法报错,调用者不能指定类型
// Cannot convert value of type 'some Shape' to specified type 'Square'
//let shape: Square = makeShape()
// 只能用some Shape
let shape: some Shape = makeShape()
        不透明类型的限制:必须返回单一类型
            
            
              swift
              
              
            
          
          struct FlippedShape: Shape {
    var shape: Shape
    init(_ shape:  Shape) {
        self.shape = shape
    }
    func draw() -> String {
        let lines = shape.draw().split(separator: "\n")
        return lines.reversed().joined(separator: "\n")
    }
}
// ❌ 错误:返回了不同类型
// Function declares an opaque return type 'some Shape', but the return statements in its body do not have matching underlying types
func invalidFlip<T: Shape>(_ shape: T) -> some Shape {
    if shape is Square {
        return shape // 返回的是 T
    }
    return FlippedShape(shape) // 返回的是 FlippedShape<T>
}
        ✅ 正确做法:统一返回类型
            
            
              swift
              
              
            
          
          func flip<T: Shape>(_ shape: T) -> some Shape {
    return FlippedShape(shape) // 总是返回 FlippedShape<T>
}
        泛型与不透明类型结合使用
            
            
              swift
              
              
            
          
          struct RepeatedShape: Shape {
    let shape: Shape
    let count: Int
    
    func draw() -> String {
        return (0..<count).map { _ in shape.draw() }.joined(separator: "\n")
    }
}
func repeatShape<T: Shape>(shape: T, count: Int) -> some Shape {
    return RepeatedShape(shape: shape, count: count)
}
        虽然 T 是泛型,但返回类型始终是 RepeatedShape<T>,满足单一类型要求。
装箱协议类型(Boxed Protocol Types)
基本概念
装箱协议类型使用 any 关键字,表示"任意遵循该协议的类型",类型信息在运行时确定,类型身份不保留。
            
            
              swift
              
              
            
          
          let shape: any Shape = Square()
        any Shape 可以存储任何遵循 Shape 的类型,类似于 Objective-C 的 id<Protocol>。
使用示例
            
            
              swift
              
              
            
          
          struct VerticalShapes {
    var shapes: [any Shape] // 可以存储不同类型的 Shape
    func draw() -> String {
        return shapes.map { $0.draw() }.joined(separator: "\n")
    }
}
        类型擦除与运行时类型检查
            
            
              swift
              
              
            
          
          struct Triangle: Shape {
    func draw() -> String {
        return "🔺"
    }
}
let shapes: [any Shape] = [Square(), Triangle()]
for shape in shapes {
    if let square = shape as? Square {
        print("这是一个方块:\(square.draw())")
    }
}
        不透明类型 vs 装箱协议类型
| 特性 | 不透明类型 some | 
装箱协议类型 any | 
|---|---|---|
| 类型身份 | 保留(编译期已知) | 不保留(运行时确定) | 
| 灵活性 | 低(只能返回一种类型) | 高(可返回多种类型) | 
| 性能 | 更好(无运行时开销) | 有装箱开销 | 
| 是否支持协议关联类型 | ✅ 支持 | ❌ 不支持 | 
代码示例对比
不透明类型版本
            
            
              swift
              
              
            
          
          func flip<T: Shape>(_ shape: T) -> some Shape {
    return FlippedShape(shape)
}
let flipped = flip(Square())
let doubleFlipped = flip(flipped) // ✅ 支持嵌套
        装箱协议类型版本
            
            
              swift
              
              
            
          
          func protoFlip(_ shape: any Shape) -> any Shape {
    return FlippedShape(shape)
}
let flipped = protoFlip(Square())
let doubleFlipped = protoFlip(flipped)  // ✅ 支持嵌套
        不透明参数类型(some 作为参数)
            
            
              swift
              
              
            
          
          func drawTwice(_ shape: some Shape) {
    print(shape.draw())
    print(shape.draw())
}
        等价于泛型函数:
            
            
              swift
              
              
            
          
          func drawTwice<S: Shape>(_ shape: S) {
    print(shape.draw())
    print(shape.draw())
}
        总结与最佳实践
| 使用场景 | 推荐类型 | 
|---|---|
| 隐藏实现细节,返回单一类型 | some Protocol | 
| 存储多种协议类型 | any Protocol | 
| 需要运行时类型判断 | any Protocol | 
在实际项目中的应用
SwiftUI 中的不透明类型
            
            
              swift
              
              
            
          
          var body: some View {
    VStack {
        Text("Hello")
        Image(systemName: "star")
    }
}
        body 返回的是某个具体类型(如 _VStack<TupleView<(Text, Image)>>),但调用者无需关心。
网络层返回模型抽象
            
            
              swift
              
              
            
          
          protocol Model {
    static func parse(from data: Data) -> Self?
}
func fetch<T: Model>(_ type: T.Type) -> some Model {
    // 隐藏具体模型类型,返回某个遵循 Model 的类型
}
        结语
不透明类型和装箱协议类型是 Swift 类型系统中非常强大的工具,它们各自解决了不同层面的抽象问题:
some更适合封装实现细节,提供类型安全的抽象接口;any更适合运行时灵活性,但牺牲类型信息和性能。
理解它们的本质区别,能帮助你在设计 API、构建模块时做出更合理的架构决策。