Swift 的协议:比接口更灵活

如果你写过 Java 或 C# 的接口,Swift 的 protocol 是个更灵活的版本。它不只是"规定这个类要实现哪些方法",而是一种"能力标签":任何类型(结构体、类、枚举)只要声明遵守某个协议,就等于贴上了"我能做这件事"的标签。

Swift 的标准库几乎全是用协议搭起来的:Array、Set、Dictionary 都遵守 Collection 协议,因此对它们写的代码几乎一样。这让 Swift 既能写得安全(编译期检查),又能写得灵活(通用算法复用)。

Swift 官方推崇"协议导向编程":先想清楚你的类型有哪些能力(协议),再让具体类型去实现它们,而不是先把类层次搭好。
本文核心知识点
  • protocol定义一组方法、属性的要求,不写实现。
  • 遵守协议在类型名后面加 : 协议名,再补全要求。
  • 协议扩展用 extension 给协议加默认实现。
  • 泛型函数用 <T> 让一份逻辑适用于多种类型。
  • associatedtype协议内部声明一个占位类型。
Swift 协议与泛型配图:紫蓝渐变背景,中央一个发光的多层协议契约方框,方框向外伸出多条发光连接线路到圆形、三角形、方形等不同形状的节点,节点之间有共享连接
中央契约核心向外连接不同形状的节点——不同类型遵守同一协议,像插同一个插座。

定义协议:规定"能做什么"

用 protocol 关键字定义一组要求。下面这个 HasArea 协议规定:任何遵守它的类型,都得有一个可读的 area 属性。

protocol HasArea {
    var area: Double { get }   // 只要能读就行
}

protocol Named {
    var name: String { get }
}

注意协议里只写"要有什么",不写"具体怎么算"。这是协议和普通类的区别:它是一份契约,不是一份实现。

让类型遵守协议

让具体类型在名字后面加 : HasArea,再把协议要求的东西补上。下面给 Circle 和 Rect 两个结构体都加上 HasArea。

struct Circle: HasArea {
    let radius: Double
    var area: Double {
        return Double.pi * radius * radius
    }
}

struct Rect: HasArea {
    let w: Double, h: Double
    var area: Double {
        return w * h
    }
}

现在 Circle 和 Rect 都"是"HasArea 了——你可以写一个函数,只要求参数是 HasArea,不关心它具体是圆还是方。

协议扩展:给协议加默认实现

Swift 有个特别好用的特性:可以用 extension 直接给协议本身加方法。这样所有遵守协议的类型,自动就白拿到这个方法,不用每个类型都写一遍。

extension HasArea {
    func isBigger(than other: HasArea) -> Bool {
        return self.area > other.area
    }
}

let c = Circle(radius: 2)
let r = Rect(w: 3, h: 3)
print(c.isBigger(than: r))   // true

Circle 和 Rect 都没自己写 isBigger,却自动能用——因为它们都遵守 HasArea。这就是协议导向编程省代码的地方。

泛型:写一次、到处用

和 Go、Rust 一样,Swift 也有泛型。写一个对任意类型数组找第一个元素的函数:

func firstElement<T>(of array: [T]) -> T? {
    return array.first
}

let nums = firstElement(of: [1, 2, 3])      // Int?
let words = firstElement(of: ["a", "b"])    // String?

<T> 是占位类型,编译器根据实参推断具体是什么。还可以加约束:"T 必须遵守 Equatable",这样函数里就能用 == 比较。

associatedtype:协议里的关联类型

有时候协议里需要一个"类型占位"——比如一个 Container 协议,它装的元素类型是什么,由遵守者自己决定。这就是 associatedtype。

protocol Container {
    associatedtype Item          // 占位:具体是什么类型由遵守者定
    var count: Int { get }
    mutating func append(_ item: Item)
}

struct IntStack: Container {
    typealias Item = Int         // 这个栈装的是 Int
    var items: [Int] = []
    var count: Int { items.count }
    mutating func append(_ item: Int) {
        items.append(item)
    }
}

Swift 标准库的 Sequence、Collection 都是这样设计的——这也是为什么 Array、Set 能被同一套算法处理。