在 Swift 中,extension 与 class 的访问控制(Access Control)结合时,遵循的是“静态安全优先”的原则。这与 Objective-C 那种“只要知道名字就能调”的松散机制完全不同。
以下是它们结合时的四大核心限制与特性:
1. Extension 无法访问私有成员(跨文件限制)
这是最容易踩坑的地方,Swift 的访问控制严格基于物理文件。
private的限制:如果你在一个文件中定义了class,在另一个文件中写了它的extension,那么在这个扩展里是无法访问类中的private属性或方法的。fileprivate的方案:如果你希望扩展能访问类的私有成员,必须将该成员标记为fileprivate。- 同文件特权:从 Swift 4.0 开始,如果你把
class和extension写在同一个 .swift 文件里,extension是可以访问类中的private成员的。
2. 扩展本身不能标记为 open
Swift 对继承链的控制非常严密:
- 限制:你不能将一个
extension标记为open。只有类的原始定义(或者类中的具体成员)可以标记为open。 - 后果:这意味着你不能在扩展中定义一个可以被模块外子类重写(Override)的方法。
- 变通:如果你希望扩展里的方法能被子类重写,该方法最多只能标记为
public,且该类必须是 Objective-C 的子类(使用@objc),但这会牺牲 Swift 的静态派发性能。
3. 访问级别的“天花板”效应
当你给整个 extension 设置访问级别时,它会成为内部所有成员的“最高限额”。
- 规则:如果你写
public extension MyClass { ... },那么其中的成员默认是internal(Swift 的默认级别),你可以手动将其提升为public。 - 限制:如果你写
internal extension MyClass { ... },即便你在里面写了public func foo(),这个foo在外部模块看来依然是不可见的。扩展的级别限制了成员的可见性。
4. 无法重写(Override)非 @objc 成员
在 Swift 的原生体系里,扩展是用来“添加”功能而非“修改”功能的。
- 限制:你不能在
extension里重写类中已经存在的非@objc方法。 - 底层原因:Swift 类的原生方法使用的是 V-Table(虚函数表) 派发或 Static Dispatch(静态派发) 。这些在编译时就固定了内存偏移量。而
extension是在之后“贴”上去的,它不在 V-Table 的预留位置里,因此无法实现安全重写。 - 对比:Objective-C 的 Category 可以覆盖方法,是因为它完全依赖运行时的
objc_msgSend。
总结:常见场景对照表
| 场景 | 是否允许 | 备注 |
|---|---|---|
同文件 Extension 访问 private | ✅ 允许 | Swift 4.0+ 的改进。 |
跨文件 Extension 访问 private | ❌ 禁止 | 必须改用 fileprivate 或 internal。 |
在 Extension 中添加 public 方法 | ✅ 允许 | 前提是 Extension 级别不低于 public。 |
| 在 Extension 中重写父类方法 | ❌ 禁止 | 除非是标记了 @objc 的类,且极其不推荐。 |
| Extension 遵循协议并实现私有方法 | ✅ 允许 | 常用作内部逻辑解耦。 |
💡 最佳实践建议
在 Swift 中,最推荐的做法是:利用 fileprivate 在同一个文件中通过 extension 实现协议。 这样既能保证逻辑分离(把 Delegate 代码分出去),又不会破坏数据的封装性。