name: Swift Protocol-Oriented Programming user-invocable: false description: 使用当涉及到 Swift 中的协议导向编程,包括协议扩展、默认实现、协议组合、关联类型,以及设计灵活、可重用的抽象,优先组合而非继承。 allowed-tools: []
Swift 协议导向编程
介绍
协议导向编程(POP)是 Swift 的范式,用于构建灵活、可组合的抽象,无需类继承的严格层次结构。协议定义接口,类型可以采纳,而协议扩展提供默认实现和功能给多种类型同时。
这种方法提供了组合的灵活性、静态分派的性能,以及扩展值类型如结构体和枚举的能力。POP 是 Swift 标准库的基础,并为代码重用、测试和 API 设计启用了强大的模式。
这个技能涵盖了协议设计、扩展、关联类型、组合,以及构建协议导向架构的实用模式。
协议基础和设计
协议定义类型必须满足的契约,指定所需的属性、方法和初始化器,而不提供实现。
// 基本协议定义
protocol Drawable {
var lineWidth: Double { get set }
var color: String { get }
func draw()
mutating func resize(by factor: Double)
}
// 在结构体中采纳协议
struct Circle: Drawable {
var lineWidth: Double
let color: String
var radius: Double
func draw() {
print("绘制半径为 \(radius) 的圆形")
}
mutating func resize(by factor: Double) {
radius *= factor
}
}
// 在类中采纳协议
class Rectangle: Drawable {
var lineWidth: Double
let color: String
var width: Double
var height: Double
init(lineWidth: Double, color: String, width: Double, height: Double) {
self.lineWidth = lineWidth
self.color = color
self.width = width
self.height = height
}
func draw() {
print("绘制 \(width)x\(height) 的矩形")
}
func resize(by factor: Double) {
width *= factor
height *= factor
}
}
// 使用协议作为类型
func render(shape: Drawable) {
print("使用 \(shape.color) 颜色渲染")
shape.draw()
}
let circle = Circle(lineWidth: 2.0, color: "red", radius: 5.0)
render(shape: circle)
// 协议的初始化器要求
protocol Identifiable {
var id: String { get }
init(id: String)
}
struct User: Identifiable {
let id: String
let name: String
init(id: String) {
self.id = id
self.name = "未知"
}
}
良好设计的协议是聚焦和连贯的,定义一个单一职责而不是混合无关的要求。
协议扩展
协议扩展提供默认实现给所有采纳类型,启用代码重用无需继承,并允许对现有类型进行追溯建模。
// 协议带扩展提供默认值
protocol Greetable {
var name: String { get }
func greet() -> String
func formalGreet() -> String
}
extension Greetable {
func greet() -> String {
return "你好, \(name)!"
}
func formalGreet() -> String {
return "日安, \(name)."
}
}
// 类型采纳协议,获得默认值
struct Person: Greetable {
let name: String
// greet() 和 formalGreet() 由扩展提供
}
// 类型可以覆盖默认值
struct Robot: Greetable {
let name: String
func greet() -> String {
return "问候, \(name.uppercased())"
}
}
// 条件扩展
extension Collection where Element: Equatable {
func allEqual() -> Bool {
guard let first = first else { return true }
return allSatisfy { $0 == first }
}
}
let numbers = [5, 5, 5, 5]
print(numbers.allEqual()) // true
// 扩展协议带约束
extension Drawable where Self: AnyObject {
func drawWithRetain() {
// 仅对类类型可用
draw()
}
}
// 添加计算属性
extension Drawable {
var description: String {
return "形状有 \(color) 颜色和 \(lineWidth)pt 线条"
}
}
// 协议扩展提供实用工具
protocol JSONRepresentable {
func toJSON() -> [String: Any]
}
extension JSONRepresentable {
func toJSONString() -> String {
let dict = toJSON()
guard let data = try? JSONSerialization.data(
withJSONObject: dict
),
let string = String(data: data, encoding: .utf8) else {
return "{}"
}
return string
}
}
协议扩展启用追溯建模——添加协议一致性到您不拥有的类型,包括标准库类型。
关联类型
关联类型创建泛型协议,允许符合类型指定满足协议要求的具体类型。
// 协议带关联类型
protocol Container {
associatedtype Item
var count: Int { get }
mutating func append(_ item: Item)
subscript(i: Int) -> Item { get }
}
// 具体类型指定 Item
struct IntStack: Container {
typealias Item = Int // 显式,但可以推断
private var items: [Int] = []
var count: Int {
return items.count
}
mutating func append(_ item: Int) {
items.append(item)
}
subscript(i: Int) -> Int {
return items[i]
}
}
// 泛型类型带关联类型
struct Stack<Element>: Container {
private var items: [Element] = []
var count: Int {
return items.count
}
mutating func append(_ item: Element) {
items.append(item)
}
subscript(i: Int) -> Element {
return items[i]
}
}
// 使用关联类型在泛型函数中
func printAll<C: Container>(_ container: C) where C.Item == String {
for i in 0..<container.count {
print(container[i])
}
}
// 关联类型带约束
protocol Graph {
associatedtype Node: Hashable
associatedtype Edge
func neighbors(of node: Node) -> [Node]
func edges(from node: Node) -> [Edge]
}
// 多个关联类型
protocol Transformable {
associatedtype Input
associatedtype Output
func transform(_ input: Input) -> Output
}
struct StringToIntTransformer: Transformable {
func transform(_ input: String) -> Int {
return Int(input) ?? 0
}
}
// 关联类型带默认值
protocol Summable {
associatedtype Result = Self
func sum(with other: Self) -> Result
}
extension Int: Summable {
func sum(with other: Int) -> Int {
return self + other
}
}
关联类型启用基于协议的泛型编程,提供灵活性同时保持类型安全和性能。
协议组合
协议组合将多个协议结合成一个单一要求,启用精确类型约束而无需创建协议层次结构。
// 个体协议
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
protocol Addressable {
var address: String { get }
}
// 函数要求多个协议
func displayInfo(for entity: Named & Aged) {
print("\(entity.name) 是 \(entity.age) 岁")
}
struct Employee: Named, Aged, Addressable {
let name: String
let age: Int
let address: String
}
let employee = Employee(name: "Alice", age: 30, address: "123 Main St")
displayInfo(for: employee)
// 组合带类
protocol Purchasable {
var price: Double { get }
}
func processPurchase(item: AnyObject & Purchasable) {
// 必须是类(AnyObject)并符合 Purchasable
print("处理购买 $\(item.price)")
}
// 协议组合在属性中
class Store {
var items: [Named & Purchasable] = []
func addItem(_ item: Named & Purchasable) {
items.append(item)
}
}
// 组合带关联类型
protocol Comparable2: Equatable {
func isLessThan(_ other: Self) -> Bool
}
func sorted<T: Comparable2>(items: [T]) -> [T] {
return items.sorted { $0.isLessThan($1) }
}
// 类型别名用于常见组合
typealias Person = Named & Aged & Addressable
func register(person: Person) {
print("注册 \(person.name)")
}
// 组合在泛型约束中
func merge<T>(_ a: T, _ b: T) -> [T] where T: Named & Aged {
return [a, b].sorted { $0.age < $1.age }
}
协议组合创建精确约束而无需深层继承层次结构的脆弱性或创建新协议的开销。
协议见证和类型擦除
类型擦除将具体类型隐藏在协议接口后面,启用异构集合和抽象实现细节。
// 问题:带关联类型的协议不能用作类型
protocol Producer {
associatedtype Item
func produce() -> Item
}
// 类型擦除包装器
struct AnyProducer<T>: Producer {
typealias Item = T
private let _produce: () -> T
init<P: Producer>(_ producer: P) where P.Item == T {
_produce = producer.produce
}
func produce() -> T {
return _produce()
}
}
// 具体生产者
struct IntProducer: Producer {
func produce() -> Int {
return 42
}
}
struct StringProducer: Producer {
func produce() -> String {
return "Hello"
}
}
// 异构数组使用类型擦除
let producers: [Any] = [
AnyProducer(IntProducer()),
AnyProducer(StringProducer())
]
// 标准库类型擦除:AnySequence
func makeSequence() -> AnySequence<Int> {
let array = [1, 2, 3, 4, 5]
return AnySequence(array)
}
// 结合类型擦除带协议组合
protocol DataSource {
associatedtype Data
func fetch() -> Data
}
struct AnyDataSource<T>: DataSource {
typealias Data = T
private let _fetch: () -> T
init<DS: DataSource>(_ source: DS) where DS.Data == T {
_fetch = source.fetch
}
func fetch() -> T {
return _fetch()
}
}
// 使用存在类型(Swift 5.7+)
protocol Animal {
func makeSound() -> String
}
struct Dog: Animal {
func makeSound() -> String { "Woof" }
}
struct Cat: Animal {
func makeSound() -> String { "Meow" }
}
let animals: [any Animal] = [Dog(), Cat()]
类型擦除以一些类型信息换取灵活性,启用协议抽象在集合和属性中作为具体类型工作。
协议导向架构模式
协议导向设计通过依赖注入和协议基于抽象支持可测试性、模块化和清洁架构。
// 依赖注入带协议
protocol NetworkService {
func fetch(url: URL) async throws -> Data
}
protocol DataStore {
func save(_ data: Data, key: String) throws
func load(key: String) throws -> Data
}
// 具体实现
struct URLSessionNetworkService: NetworkService {
func fetch(url: URL) async throws -> Data {
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
}
struct UserDefaultsDataStore: DataStore {
func save(_ data: Data, key: String) throws {
UserDefaults.standard.set(data, forKey: key)
}
func load(key: String) throws -> Data {
guard let data = UserDefaults.standard.data(forKey: key) else {
throw DataStoreError.notFound
}
return data
}
}
enum DataStoreError: Error {
case notFound
}
// 业务逻辑依赖于协议
class Repository {
private let network: NetworkService
private let store: DataStore
init(network: NetworkService, store: DataStore) {
self.network = network
self.store = store
}
func fetchAndCache(url: URL, key: String) async throws {
let data = try await network.fetch(url: url)
try store.save(data, key: key)
}
}
// 测试带模拟实现
struct MockNetworkService: NetworkService {
let mockData: Data
func fetch(url: URL) async throws -> Data {
return mockData
}
}
struct MockDataStore: DataStore {
var storage: [String: Data] = [:]
mutating func save(_ data: Data, key: String) throws {
storage[key] = data
}
func load(key: String) throws -> Data {
guard let data = storage[key] else {
throw DataStoreError.notFound
}
return data
}
}
// 策略模式带协议
protocol SortStrategy {
func sort<T: Comparable>(_ array: [T]) -> [T]
}
struct QuickSort: SortStrategy {
func sort<T: Comparable>(_ array: [T]) -> [T] {
guard array.count > 1 else { return array }
// 快速排序实现
return array.sorted()
}
}
struct BubbleSort: SortStrategy {
func sort<T: Comparable>(_ array: [T]) -> [T] {
// 冒泡排序实现
return array.sorted()
}
}
class Sorter {
var strategy: SortStrategy
init(strategy: SortStrategy) {
self.strategy = strategy
}
func sort<T: Comparable>(_ array: [T]) -> [T] {
return strategy.sort(array)
}
}
协议导向架构通过允许模拟实现提高可测试性,并通过启用运行时策略更改支持灵活性。
最佳实践
-
设计小、聚焦的协议,带有单一职责而不是混合无关要求的大型协议
-
提供默认实现在扩展中以减少样板代码并允许采纳类型选择性定制
-
优先协议组合而非继承以创建精确约束而无需脆弱层次结构
-
使用关联类型用于泛型协议当采纳类型需要为要求指定具体类型时
-
应用协议扩展条件性带 where 子句以为受约束类型提供专门行为
-
利用值类型带协议以获得组合好处而无需引用语义或继承限制
-
创建类型擦除包装器用于带关联类型的协议当需要异构集合或抽象时
-
设计用于可测试性通过在业务逻辑中依赖协议抽象而非具体类型
-
使用协议见证用于依赖注入以解耦组件并启用灵活配置
-
明确文档化协议语义,包括性能期望和使用约束超出类型签名
常见陷阱
-
创建过于广泛的协议混合无关关注导致强制实现和接口隔离违反
-
忘记 mutating 关键字在修改值类型的协议方法上导致编译错误在结构体实现中
-
协议扩展阴影其中扩展中的方法不覆盖实现,使用静态分派代替
-
不充分约束关联类型允许采纳类型选择不适当的具体类型
-
过度使用类型擦除当更简单的解决方案存在时添加复杂性并隐藏实际类型不必要
-
忽略协议 vs 类分派差异导致意外行为当协议使用扩展和类使用继承时
-
创建模仿类的协议层次结构违背协议导向编程的组合好处目的
-
不提供默认实现当大多数采纳类型会使用相同逻辑时浪费重用机会
-
为一切使用协议当具体类型足够时添加抽象开销没有有意义的益处
-
未能彻底测试协议一致性允许满足签名但违反语义的实现中的错误
何时使用此技能
使用协议导向编程当构建需要跨值类型和类的灵活性、可测试性和代码重用的 Swift 应用程序时。这适用于 iOS、macOS、watchOS、tvOS 和服务器端 Swift 开发。
应用协议和扩展当设计需要支持多个实现或允许客户端自定义行为而无需子类化的框架、库或模块时。
使用协议组合当为函数和属性创建精确类型约束时,特别是在需要操作满足多个要求的类型的泛型代码中。
利用关联类型当构建如集合、转换器或数据源的泛型抽象时,其中采纳类型需要指定具体类型。
使用基于协议的依赖注入在架构模式如 MVVM、VIPER 或 Clean Architecture 中以提高可测试性和解耦组件。