Swift 面向协议编程(POP)

前言

如果你写过一段时间的 Swift,大概率听过这句话:"Swift 是面向协议的语言"​ 。相比 Objective‑C 时代重度依赖继承(OOP),Swift 给了我们另一种更灵活、更安全的武器------面向协议编程(Protocol‑Oriented Programming,简称 POP)。

本博客将深入解析 Swift 中的 POP(面向协议编程)

一、为什么 Swift 要推 POP?

先看一个经典 OOP 场景:

复制代码
class Animal {
    func move() {
        print("Animal moves")
    }
}

class Dog: Animal {
    override func move() {
        print("Dog runs")
    }
}

class Fish: Animal {
    override func move() {
        print("Fish swims")
    }
}

看起来没问题,但当你想给"会飞的动物"加行为时,问题来了:

复制代码
class Bird: Animal {
    func fly() { print("Bird flies") }
}

Bat(蝙蝠)呢?它也是哺乳动物,却也会飞。

如果继续用继承,很快就会出现 多层继承 + 上帝类,维护成本急剧上升。

继承的问题:

  • 强耦合:子类依赖父类实现

  • 复用困难:跨继承树的行为很难共享

  • 难以组合:一个类只能有一个父类

这正是 POP 想要解决的痛点。


二、协议+协议扩展 = POP的核心

Swift 中,协议不再只是"接口定义",而是可以:

  • 声明属性和方法

  • 提供默认实现

  • 作为类型约束

  • 进行协议组合

1.用协议描述能力

我们把上面的例子重构成 POP 风格:

复制代码
protocol Movable {
    func move()
}

struct Dog: Movable {
    func move() {
        print("Dog runs")
    }
}

struct Fish: Movable {
    func move() {
        print("Fish swims")
    }
}

此时,Movable描述的是一种 能力,而不是"是什么"。

2.协议扩展:默认实现的魔法

这是 POP 最迷人的地方。

复制代码
protocol Flyable {
    func fly()
}

extension Flyable {
    func fly() {
        print("Default flying behavior")
    }
}

任何遵循 Flyable的类型,都可以直接使用默认实现:

复制代码
struct Bird: Flyable {}

let bird = Bird()
bird.fly() // Default flying behavior

需要定制行为?直接重写即可:

复制代码
struct Eagle: Flyable {
    func fly() {
        print("Eagle soars high")
    }
}

没有继承

没有override风险

行为可组合

三、用协议组合替代多重继承

Swift 不支持多重继承,但 POP 提供了更好的方案。

1.不推荐的"上帝类"

复制代码
class SuperCreature {
    func run() {}
    func swim() {}
    func fly() {}
}

2.POP推荐方式:能力拆分+组合

复制代码
protocol Runnable {}
protocol Swimmable {}
protocol Flyable {}

extension Runnable {
    func run() { print("Running") }
}

extension Swimmable {
    func swim() { print("Swimming") }
}

extension Flyable {
    func fly() { print("Flying") }
}

然后自由组合:

复制代码
struct Duck: Runnable, Swimmable, Flyable {}

let duck = Duck()
duck.run()
duck.swim()
duck.fly()

组合优于继承在这里体现得淋漓尽致。

四、协议作为类型 & 泛型约束

1.Existential Type(存在类型)

复制代码
let movers: [Movable] = [Dog(), Fish()]
for mover in movers {
    mover.move()
}

注意:使用协议作为变量类型会有一定的 动态派发开销,在性能敏感场景要谨慎。

2.泛型 + where 子句

复制代码
func makeMove<T: Movable>(_ mover: T) {
    mover.move()
}

好处:

  • 编译期确定类型

  • 无运行时开销

  • 自动支持值语义(struct / enum)

五、POP 在真实项目中的典型用法

1. Cell 配置解耦(非常常见)

复制代码
protocol ConfigurableCell {
    associatedtype Model
    func configure(with model: Model)
}

struct UserCell: ConfigurableCell {
    typealias Model = User

    func configure(with model: User) {
        // 绑定 UI
    }
}

👉 View 和 Model 彻底解耦。


2. Service / Repository 抽象

复制代码
protocol UserService {
    func fetchUser(completion: (User?) -> Void)
}

class NetworkUserService: UserService {
    func fetchUser(completion: (User?) -> Void) {
        // 网络请求
    }
}

测试时只需注入 Mock:

复制代码
class MockUserService: UserService {
    func fetchUser(completion: (User?) -> Void) {
        completion(User(name: "Test"))
    }
}

3. 动画 / 主题 / 策略模式

复制代码
protocol AnimationStrategy {
    func animate(view: UIView)
}

struct FadeAnimation: AnimationStrategy {
    func animate(view: UIView) {
        UIView.animate(withDuration: 0.3) {
            view.alpha = 1
        }
    }
}

六、POP vs OOP:什么时候用哪个?

场景 推荐
描述能力 / 行为 ✅ POP
强层级关系(is-a) ✅ OOP
UIKit / AppKit 组件 ✅ OOP(必须继承)
业务模型 / 工具类 ✅ POP
需要默认实现 ✅ POP
需要状态共享 ⚠️ 谨慎 POP

一句话总结:

能用协议组合解决的问题,就不要用继承。


七、POP 的几个坑 & 注意事项

1. Protocol 不能直接存储属性

复制代码
protocol Foo {
    var name: String { get set } // 只能是计算属性
}

2. associatedtype 会让协议不能当普通类型使用

复制代码
// ❌ 错误
let cells: [ConfigurableCell]

解决方式:

  • 使用类型擦除(AnyConfigurableCell

  • 或用泛型容器


3. 协议膨胀(Protocol Explosion)

不要为了"看起来很 POP"而拆太多协议。

协议应该是有意义的抽象,而不是语法体操。

八、实际应用

Swift的POP 并不是要干掉OOP,而是给我们一套更现代、更安全的工具箱:

  • 用协议​描述能力

  • 用协议扩展​提供默认实现

  • 用组合​构建复杂行为

  • 用值类型​降低副作用

我们可以通过一个loading的例子,来说明下POP的实际应用场景。

很多页面都有loading。

传统写法如下:

Swift 复制代码
class UserVC: UIViewController {

}

class RepoVC: UIViewController {

}

class FollowerVC: UIViewController {

}

每个页面都写:

showLoading()

hideLoading()

重复代码很多。

我们可以使用协议定义show和hide行为。

protocol Loadingable {

func showLoading()

func hideLoading()

}

默认实现

extension Loadingable where Self: UIViewController {

func showLoading() {

print("显示Loading")

}

func hideLoading() {

print("隐藏Loading")

}

}

页面遵守协议

final class UserVC:

UIViewController,

Loadingable {

}

直接使用:

let vc = UserVC()

vc.showLoading()

vc.hideLoading()