编程iOS开发者

什么是Swift中的协议扩展,它们有什么用?它们如何与面向协议的编程集成,以及在使用默认实现时可能出现哪些潜在问题?

用 Hintsage AI 助手通过面试

答案。

协议扩展是在Swift中引入的,用于支持面向协议的编程理念:程序员可以直接在协议层面上引入默认方法的实现,而不是通过基类或全局函数。这减少了代码重复并允许灵活地调整类型的行为。

问题出现在默认实现掩盖了需要自定义(重写)的必要性,或者责任线变得模糊——特别是当一个类型实现多个有交集的协议时。此外,重要的是要记住:如果在类型本身中实现了方法,它将始终覆盖扩展中的实现。

解决方案是仅在协议扩展中使用通用行为,而在特定情况下明确实现类型内的方法。最好避免在同一个协议的两个扩展中重载相同的方法。

示例代码:

protocol Flyer { func fly() } extension Flyer { func fly() { print("默认飞行") } } struct Bird: Flyer {} let sparrow = Bird() sparrow.fly() // 输出:默认飞行

关键特性:

  • 允许提供通用功能并减少重复,无需继承。
  • 默认实现仅在类型没有明确定义其方法时有效。
  • 允许使用泛型和where约束来明确特定类型的行为。

具有挑战性的问题。

协议扩展可以为协议添加存储属性吗?

不可以,协议扩展只能添加计算属性和方法。存储属性是不允许的。

如果协议和类型有不同实现的方法同名,会发生什么?哪个会被调用?

当直接调用类型时,会调用类型的实现;通过协议类型引用调用实例时,会调用协议扩展的实现。

protocol Greeter { func greet() } extension Greeter { func greet() { print("来自扩展的问候") } } struct Person: Greeter { func greet() { print("来自类型的问候") } } let person = Person() person.greet() // 来自类型的问候 let greeter: Greeter = person greeter.greet() // 来自扩展的问候

可以在协议扩展中使用where约束吗?

可以。这是一个强大的特性——协议可以仅拓展特定类型。

extension Collection where Element: Equatable { func allEqual() -> Bool { guard let first = self.first else { return true } return allSatisfy { $0 == first } } }

常见错误和反模式

  • 使用协议扩展实现行为,期望在具体类型中重写:协议扩展不支持重写,这是非类的。
  • 覆盖类型的方法和扩展的方法(不同的签名或业务逻辑)可能导致意外的多态行为。

生活中的例子

负面案例

团队决定通过协议扩展实现错误日志记录,而没有考虑到每个服务可能想要添加其特定格式。结果不同的服务通过协议引用调用函数,行为逻辑与预期相差很大。

优点:

  • 代码少,基础实现易于维护。

缺点:

  • 在运行时多态中会出现意外情况,意图与执行不一致,生产中的缺陷。

积极案例

仅在行为始终通用的情况下扩展协议。对于特定情况——在类型中明确实现方法,代码审查对冲突点进行审核。

优点:

  • 逻辑清晰,调用错误最少,通用性仅在应有的地方工作。

缺点:

  • 需要了解细节,无法完全避免在个别案例中的复制粘贴。