自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 }
关键特点:
let x: MyContainer // 错误),如果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的协议用于数据模型,随后尝试创建数组let boxes: [MyContainer],结果编译器报错。最终不得不引入额外的包装,失去类型安全。
优点:
缺点:
在项目中,带有associatedtype的协议用于实现集合,并创建单独的类型擦除封装AnyCollection来抽象与之工作的方式。这使得在视图模型层中能够抽象集合的具体实现,而不失去业务逻辑层的类型安全。
优点:
缺点: