一、场景一:foo() 在 protocol extension 中
1️⃣ 定义协议 + extension
protocol Fooable {}
extension Fooable {
func foo() {
print("foo from protocol extension")
}
}
2️⃣ 具体类型“实现”同名方法
struct A: Fooable {
func foo() {
print("foo from A")
}
}
⚠️ 注意:
这里并不是 override
只是“恰好同名的普通方法”
3️⃣ 不同调用方式 → 不同结果
✅ 通过具体类型调用
let a = A()
a.foo()
输出:
foo from A
原因:
👉 编译器知道 a 是 A
👉 直接静态绑定 A.foo()
❌ 通过协议类型调用(关键)
let p: Fooable = A()
p.foo()
输出:
foo from protocol extension
🔥 同一个对象,结果变了
4️⃣ 为什么?
因为:
foo()没有写在 protocol 里- protocol 并不知道有
foo() - extension 里的
foo()是 静态派发 - 不走 witness table
- 编译期直接绑定 extension 实现
📌 struct A 的 foo() 根本不参与多态
二、场景二:foo() 在 class 继承体系中
现在我们用 class,做完全一样的事情。
1️⃣ 基类定义 foo()
class Base {
func foo() {
print("foo from Base")
}
}
2️⃣ 子类 override
class B: Base {
override func foo() {
print("foo from B")
}
}
3️⃣ 通过父类类型调用
let b: Base = B()
b.foo()
输出:
foo from B
4️⃣ 为什么这里“没问题”?
因为:
- class 方法默认走 vtable
override是语言级语义- 调用是 动态派发
- 运行时根据真实类型决定
👉 这才是传统 OOP 的多态
三、把两种情况放在一起对比(核心)
| 场景 | 变量静态类型 | 调用机制 | 输出 |
|---|---|---|---|
| protocol extension | Fooable | 静态派发 | extension 实现 |
| protocol extension | A | 静态派发 | A.foo() |
| class override | Base | 动态派发 | B.foo() |
四、如果你“想让协议也像 class 一样”怎么办?
✅ 正确写法:把 foo() 写进 protocol
protocol Fooable {
func foo()
}
extension Fooable {
func foo() {
print("foo from default implementation")
}
}
struct A: Fooable {
func foo() {
print("foo from A")
}
}
现在:
let p: Fooable = A()
p.foo()
输出:
foo from A
🎯 因为:
foo()是协议要求- 进入 witness table
- 动态派发生效
五、一句话点破本质(非常重要)
protocol extension 里的方法不是“可 override 的接口”,
它只是“在协议命名空间下的静态函数”。
而:
class 的方法才是天生为 override 和多态设计的。
六、记住这个判断公式(以后秒懂)
看到一个 foo(),你只问两件事:
- 它写在 protocol 本体里了吗?
- 我是通过 protocol 类型调用的吗?
- ① NO + ② YES → ❌ extension 实现
- ① YES → ✅ 多态
- class + override → ✅ 多态
七、终极总结(可以当面试答案)
func foo()
在 protocol extension 中是 静态派发的默认实现,
在 class 中是 通过 vtable 动态派发的 override 点,
所以即使名字一样,语义完全不同。