前言
如果你写过一段时间的 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()