Swift如何定义包含泛型Collection的Protocol兼容Realm List和数组等不同实现?
解决方案
方案1:Swift 5.7+(Xcode 14+,最优解)
Swift 5.7 引入了**主关联类型(Primary Associated Type)**特性,标准库的Collection协议已经声明了主关联类型Element,所以你可以直接使用带约束的存在类型(existential type)实现需求,既不需要数组转换,也不需要关联类型:
protocol Item { var id: String { get } } protocol Content { // 直接约束Collection的元素为Item类型,无需关联类型 var items: any Collection<Item> { get } }
你的业务代码不需要做额外修改,Realm的List、Swift原生的Array/Set都天然满足约束:
// Realm实现 struct RealmArticle: Object, Content { var items = List<Item>() // 自动满足协议要求 } // Mock实现 struct MockArticle: Content { var items: [Item] // 自动满足协议要求 }
之前报错的类型声明也可以直接正常使用,支持自由替换不同的Content实现:
struct Parent { // 加any关键字标记为存在类型即可 var content: (any Content)? } var parent = Parent() parent.content = RealmArticle() parent.content = MockArticle()
该方案不会触发全量数组转换,Realm的List可以保持懒加载特性,性能最优。如果你的项目最低支持iOS 16/macOS 13及以上系统,优先选这个方案。
方案2:兼容Swift 5.6及更低版本
如果需要兼容旧版本Swift,你可以手动实现类型擦除包装器,擦除具体的集合类型,只暴露元素为Item的集合接口:
// 类型擦除的Item集合包装器 struct AnyItemCollection: Collection { typealias Element = Item typealias Index = AnyHashable private let _startIndex: () -> Index private let _endIndex: () -> Index private let _indexAfter: (Index) -> Index private let _subscript: (Index) -> Element init<C: Collection>(_ collection: C) where C.Element == Item { _startIndex = { AnyHashable(collection.startIndex) } _endIndex = { AnyHashable(collection.endIndex) } _indexAfter = { AnyHashable(collection.index(after: $0.base as! C.Index)) } _subscript = { collection[$0.base as! C.Index] } } var startIndex: Index { _startIndex() } var endIndex: Index { _endIndex() } func index(after i: Index) -> Index { _indexAfter(i) } subscript(position: Index) -> Item { _subscript(position) } }
然后修改Content协议的声明:
protocol Content { var items: AnyItemCollection { get } }
各个实现只需要把自身的集合属性包装一层即可,不会触发全量加载:
// Realm实现 struct RealmArticle: Object, Content { private var _items = List<Item>() var items: AnyItemCollection { AnyItemCollection(_items) } } // Mock实现 struct MockArticle: Content { private var _items: [Item] var items: AnyItemCollection { AnyItemCollection(_items) } }
原理说明
- Swift 5.7之前,带关联类型的协议(PAT,Protocol with Associated Type)无法直接作为类型使用,只能作为泛型约束,原因是编译器无法在运行时确定关联类型的内存布局和方法调用地址,这就是你之前用关联类型声明
Content后,无法直接写var content: Content?的原因。 - 你最初把
items声明为[Item]的方案,每次访问都会把Realm的List全量复制到数组中,数据量大时会有明显的内存和性能损耗。 - 主关联类型特性允许给协议的核心关联类型添加标记,使得存在类型可以直接约束关联类型的具体值,才支持
any Collection<Item>这种写法,完美匹配你的需求。
内容的提问来源于stack exchange,提问作者akaffe
相关产品推荐
相关产品推荐

