编程iOS开发者

什么是Swift中的protocol associated types,它们与泛型参数有什么区别?何时以及为何在协议中使用associatedtype?

用 Hintsage AI 助手通过面试

答案。

问题的历史

自Swift的首次发布以来,协议中的自定义泛型参数就以关键字associatedtype的形式出现,以在抽象行为时提供更大的类型控制。

问题

如果协议声明了一个要求,必须定义某种类型(例如容器中的元素类型)——没有associatedtype,使用具有抽象性的协议变得不可能:类型必须被严格固定,失去了编写通用代码的可能性。

解决方案

associatedtype允许在协议中声明一个相关类型,该类型由协议的具体实现定义。这使得在编译阶段不知道具体类型的情况下能够编写协议和泛型函数。

带有associatedtype的协议示例:

protocol MyContainer { associatedtype Item var count: Int { get } mutating func append(_ item: Item) subscript(i: Int) -> Item { get } } struct IntStack: MyContainer { var items = [Int]() mutating func append(_ item: Int) { items.append(item) } var count: Int { items.count } subscript(i: Int) -> Int { items[i] } // Swift自动推导Item=Int }

关键特点:

  • 允许创建带有类型参数的协议(更接近于C++中的模板概念),
  • 不能直接将包含associatedtype的协议"作为类型"使用(let x: MyContainer // 错误),
  • 适合用于构建复杂的泛型集合、代数结构和API。

复杂性问题。

如果Swift中已经有泛型,那么associatedtype还有什么用?

回答:泛型和associatedtype解决相似但不同的问题。泛型参数应用于类型或函数,使得可以创建通用结构和方法。而associatedtype是协议内的抽象,要求实现的类型确定自己的类型。使用泛型来实现通用算法或容器,而使用associatedtype来通过协议规范行为。

可以将带有associatedtype的协议用作变量的类型吗?

不可以,Swift不允许将此类协议直接用作类型,因为关于associatedtype的信息会丢失。可以通过类型擦除或使用泛型约束来进行抽象:

func printElements<C: MyContainer>(container: C) { for i in 0..<container.count { print(container[i]) } }

如何将带有associatedtype的协议"转为类型"(类型擦除)?

为此使用类型擦除模式——创建一个封装器,通过闭包或箱子存储内部实现。

struct AnyContainer<T>: MyContainer { private let _append: (T) -> Void private let _count: () -> Int private let _subscript: (Int) -> T ... // 通过闭包初始化 }

常见错误和反模式

  • 尝试直接将带有associatedtype的协议作为类型使用,
  • 在没有必要的情况下声明associatedtype——当泛型参数会更简单时,
  • 在不同协议中使用相同名称的相关类型而没有显式绑定到一个类型。

生活中的例子

消极案例

开发人员创建了多个带有associatedtype的协议用于数据模型,随后尝试创建数组let boxes: [MyContainer],结果编译器报错。最终不得不引入额外的包装,失去类型安全。

优点:

  • 设计阶段的灵活性

缺点:

  • 类型转换时出现严重问题
  • 维护和重构的复杂性

积极案例

在项目中,带有associatedtype的协议用于实现集合,并创建单独的类型擦除封装AnyCollection来抽象与之工作的方式。这使得在视图模型层中能够抽象集合的具体实现,而不失去业务逻辑层的类型安全。

优点:

  • 业务逻辑的明确类型
  • 替换集合实现的可能性

缺点:

  • 类型擦除引入了一些复杂的代码