编程iOS 开发者

解释一下 Swift 中协议抽象是如何工作的,并且它与类继承有何不同?什么时候应该使用协议而不是类层次结构?

用 Hintsage AI 助手通过面试

答复。

问题背景:

协议抽象在 Swift 中出现,是为了对抗经典的面向对象编程中的对象继承。如果在 Objective-C 和其他面向对象语言中,通常采用“从一般到特殊”的继承方法,那么 Swift 从一开始就将协议作为实现抽象的主要方式,强调组合优于继承。

问题:

经典的继承假设了严格的层次结构:有必要通过 override 进行扩展的子类树。这限制了灵活性,导致“脆弱”的代码、复杂的重构以及基类的膨胀。此外,Swift 不支持类的多重继承——这意味着功能的重用只能通过其他机制实现。

解决方案:

协议抽象允许声明“需求集”,类型必须实现。协议可以扩展 (extension),以注入通用逻辑,使其更接近“混入”的概念:

代码示例:

protocol Drawable { func draw() } extension Drawable { func draw() { print("默认绘图") } } struct Circle: Drawable {} let c = Circle() c.draw() // 将输出 "默认绘图"

主要特点:

  • 协议支持多重组合(多个要求)。
  • 不创建严格的层次结构 — 更易扩展,不破坏架构。
  • 可以与值类型(struct/enum)及类一起工作,这是继承所无法做到的。

带陷阱的问题。

协议的扩展和类中的普通实现之间有什么区别?

通过扩展协议添加的默认实现仅在用户未在其类型中实现该方法的情况下生效。如果类型中显式实现了该方法,则调用的是该实现。

示例:

protocol Demo { func foo() } extension Demo { func foo() { print("默认") } } struct X: Demo { func foo() { print("自定义") } } X().foo() // "自定义"

如果以协议数据的方式调用实现协议的类型及其扩展,会发生什么?

如果协议声明了一个方法为强制性(要求),即使转换为协议类型,也会使用具体类型的实现。然而,如果在扩展中添加了新的(在协议中未声明的)属性,则该属性只能通过扩展访问,而不能通过协议类型访问。

可以将实现同一协议的不同结构体的实例存储在数组中吗?

可以——通过“存在类型”(例如,[Drawable]),可以存储异构集合:

struct Tri: Drawable { func draw() { print("三角形") } } let arr: [Drawable] = [Circle(), Tri()] arr.forEach { $0.draw() }

常见错误和反模式

  • 从庞大的超类继承以获得共同接口,而不是将功能分离为协议
  • 过度使用扩展而没有明确的协议要求
  • 尝试在集合中存储与 associatedtype 的协议([SomeProtocol]) — 这并不被支持

生活中的例子

负面案例

公司中有一个基础超类 Shape,所有图形(Circle,Square,Polygon)都从其继承。基类膨胀,因为每添加一个新图形就必须通过 override 进行维护。扩展系统变得越来越困难——每个新类型破坏 ABI,并迫使重写现有代码。

优点:

  • 迅速通过基类引入新的共同方法

缺点:

  • 单一庞大的层次结构,依赖关系过多
  • 在层次结构外的重新使用效果不佳
  • override方法冲突

正面案例

开始使用多个协议:DrawableColorableAnimatable。现在每个图形都可以轻松同时变为“可动画和上色”,而不更改其他结构。新的功能通过扩展添加。

优点:

  • 灵活性、易于维护和扩展
  • 在不同上下文中的良好重用性

缺点:

  • 需要仔细设计 API 和理解 associatedtype