Swift中`any RLActionImpl`可传入`some RLActionImpl`参数的矛盾现象求解(附RealityKit代码示例)
any RLActionImpl可传入some RLActionImpl参数的矛盾现象求解(附RealityKit代码示例) 嘿,这个问题我刚摸Swift的any和some语法时也卡过好久,咱们一步步拆解来搞懂它~
先回顾你的问题场景
首先看报错的代码片段:
import RealityKit protocol RLActionImpl {} struct RLAction<Impl: RLActionImpl> { let impl: Impl } struct RLAggregationActionImpl: RLActionImpl { enum Aggregation { case sequence, group } let impls: [RLActionImpl] // 这里实际是[any RLActionImpl] let aggregation: Aggregation func process(entity: Entity) { impls.forEach { impl in // ❌ 报错:Type 'any RLActionImpl' cannot conform to 'RLActionImpl' let action = RLAction(impl: impl) entity.runAction(action) } } } extension Entity { func runAction(_ action: RLAction<some RLActionImpl>) { // 业务逻辑 } }
然后你改成用辅助函数就正常运行了:
extension Entity { func runImpl(_ impl: some RLActionImpl) { let action = RLAction(impl: impl) runAction(action) } } // 在process里调用 func process(entity: Entity) { impls.forEach { impl in // ✅ 正常运行 entity.runImpl(impl) } }
核心原因:any和some的本质区别,以及泛型的推导逻辑
咱们拆解几个关键点:
any RLActionImpl是什么?
它是存在类型(Existential Type),相当于一个“盒子”,里面装着任意一个符合RLActionImpl协议的具体类型,但在process函数里,编译器不知道这个盒子里具体装的是RLAggregationActionImpl还是其他实现类——它只知道里面是个符合协议的东西,但这个盒子本身并不符合RLActionImpl协议,所以直接把它传给RLAction<Impl: RLActionImpl>的泛型参数时,编译器会报错,因为Impl要求是一个具体的、符合协议的类型,而不是这个装类型的盒子。some RLActionImpl在函数参数里的本质?
你可能以为它是个“模糊的类型”,但其实它是泛型的语法糖!比如:func runImpl(_ impl: some RLActionImpl) { ... }等价于:
func runImpl<T: RLActionImpl>(_ impl: T) { ... }也就是说,这个函数会在调用时,根据传入的具体类型自动推导
T是什么。为什么
any RLActionImpl能传给some RLActionImpl?
当你遍历impls(也就是[any RLActionImpl])时,每个impl虽然在process里是存在类型,但它实际指向的是一个具体的实现类型(比如某个自定义的RLActionImpl结构体)。当你把它传给runImpl时,Swift会在这个调用点自动拆箱,推导出来T就是那个具体的类型,然后为这个T实例化泛型函数runImpl——这时候impl的类型是具体的T,自然符合RLActionImpl,所以能创建RLAction<T>,再传给runAction(而RLAction<T>正好匹配RLAction<some RLActionImpl>的要求,因为some在这里表示“某个具体的符合协议的类型”)。
总结一下
本质上,你是通过泛型函数(runImpl)把“处理具体类型”的逻辑延迟到了调用点,让编译器在那里推导实际的类型,从而绕过了存在类型不能直接作为泛型参数的限制。而直接在process里创建RLAction时,编译器没有办法知道impl的具体类型,所以无法满足RLAction的泛型约束。
如果嫌辅助函数麻烦,其实也可以直接用泛型写法替代,效果是一样的:
extension Entity { func runAction<T: RLActionImpl>(_ impl: T) { let action = RLAction(impl: impl) // 原来的runAction逻辑放在这里 } }
这样直接调用entity.runAction(impl)也能正常运行~




