You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Swift中`any RLActionImpl`可传入`some RLActionImpl`参数的矛盾现象求解(附RealityKit代码示例)

Swift中any RLActionImpl可传入some RLActionImpl参数的矛盾现象求解(附RealityKit代码示例)

嘿,这个问题我刚摸Swift的anysome语法时也卡过好久,咱们一步步拆解来搞懂它~

先回顾你的问题场景

首先看报错的代码片段:

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)
    }
}

核心原因:anysome的本质区别,以及泛型的推导逻辑

咱们拆解几个关键点:

  1. any RLActionImpl是什么?
    它是存在类型(Existential Type),相当于一个“盒子”,里面装着任意一个符合RLActionImpl协议的具体类型,但在process函数里,编译器不知道这个盒子里具体装的是RLAggregationActionImpl还是其他实现类——它只知道里面是个符合协议的东西,但这个盒子本身并不符合RLActionImpl协议,所以直接把它传给RLAction<Impl: RLActionImpl>的泛型参数时,编译器会报错,因为Impl要求是一个具体的、符合协议的类型,而不是这个装类型的盒子。

  2. some RLActionImpl在函数参数里的本质?
    你可能以为它是个“模糊的类型”,但其实它是泛型的语法糖!比如:

    func runImpl(_ impl: some RLActionImpl) { ... }
    

    等价于:

    func runImpl<T: RLActionImpl>(_ impl: T) { ... }
    

    也就是说,这个函数会在调用时,根据传入的具体类型自动推导T是什么。

  3. 为什么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)也能正常运行~

火山引擎 最新活动