Swift中带associatedtype协议与Opaque Return Type问题求解
解决方案:带关联类型协议的类型擦除实践
问题核心原因
Swift 中带associatedtype或者用到Self的协议属于存在型协议(PAT),不能直接作为类型声明使用,仅能作为泛型约束;而some不透明返回类型要求函数所有返回分支的实际类型完全一致,不同的协议遵守者即使满足同一协议约束,也不符合类型匹配要求。
可行解决方案(完全不修改原有定义代码)
标准解决方案是实现类型擦除包装层,把关联类型的约束隐藏在包装层内部,对外暴露统一的无泛型类型:
struct AnyAnimal: AnimalProtocol { // 关联类型直接声明为NameProtocol,满足AnimalProtocol的约束要求 typealias Name = NameProtocol // 内部持有原实例的方法和属性访问闭包,隐藏实际类型 private let _cry: () -> String private let _getName: () -> Name var name: Name { _getName() } func cry() -> String { _cry() } // 泛型初始化方法,接受所有遵守AnimalProtocol的实例 init<T: AnimalProtocol>(_ animal: T) { self._getName = { animal.name } self._cry = { animal.cry() } } }
问题1:字典存储AnimalProtocol实例
把字典的值类型声明为AnyAnimal,存储时对实例做一层包装即可:
let animals: [String: AnyAnimal] = [ "cat": AnyAnimal(Cat(name: CatName(literal: .Sam))), "dog": AnyAnimal(Dog(name: DogName(literal: .Jack))) ] for (animal, entity) in animals { print("\(animal): \(entity.cry())") }
运行后可以正常输出预期结果,cry()、name.rawValue等属性方法都可以正常调用。
问题2:函数返回不同类型的AnimalProtocol实例
把返回值类型改为AnyAnimal,每个返回分支做一层包装即可:
func animalCry(animal: String) -> AnyAnimal { switch animal { case "cat": return AnyAnimal(Cat(name: CatName(literal: .Sam))) default: return AnyAnimal(Dog(name: DogName(literal: .Toby))) } }
集合存储带内嵌泛型不同类型的最佳实践
- 优先使用类型擦除Wrapper:也就是上述演示的
AnyXXX模式,是Swift官方标准库通用的实现方案,比如系统内置的AnyHashable、AnySequence都用了相同的逻辑,不会侵入原有业务代码,符合开闭原则,新增协议遵守者时不需要修改包装层代码。 - 如果所有协议遵守者都是类类型,也可以抽离无关联类型的父类,但灵活性远低于类型擦除,且会强制业务类使用继承,侵入性较高。
- 仅在不需要对外暴露关联类型相关能力时,可以选择把协议的关联类型去掉,直接用约束类型声明属性,该方案需要修改原有协议定义,不适用于现有代码不可修改的场景。
内容的提问来源于stack exchange,提问作者Silo QIAN
相关产品推荐
相关产品推荐

