You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift中如何在编译时约束泛型类型T符合泛型类型U?

编译时约束Factory测试扩展的类型一致性

问题背景

我正在用Factory库为测试用例编写自动分配的工厂注册扩展,当前代码如下:

import XCTest
import Factory

protocol Mock {
    init()
}

protocol XCTestCaseProtocol: XCTestCase { }

extension XCTestCase: XCTestCaseProtocol { }

extension XCTestCaseProtocol {
    /// 将默认初始化器分配给测试用例类上指定的mock变量,然后将其注册为指定工厂的解析对象。
    @MainActor
    func registerAndAssignMock<T, U>(_ mockKeyPath: ReferenceWritableKeyPath<Self, T>,
                                     asFactory factoryKeyPath: KeyPath<Container, Factory<U>>) throws
    where T: Mock {
        self[keyPath: mockKeyPath] = .init()
        
        guard self[keyPath: mockKeyPath] is U else {
            // 注:据我所知,编写此函数时无法在编译时约束T符合U。
            throw XCTestCaseCustomError.typeMismatch
        }
        
        Container.shared[keyPath: factoryKeyPath].register { @MainActor [unowned self] in
            return self[keyPath: mockKeyPath] as! U
        }
    }
}

enum XCTestCaseCustomError: Error {
    case typeMismatch
}

我希望通过where子句在编译时约束T符合U,替代当前的运行时检查,尝试修改函数签名如下:

func registerAndAssignMock<T, U>(_ mockKeyPath: ReferenceWritableKeyPath<Self, T>,
                                 asFactory factoryKeyPath: KeyPath<Container, Factory<U>>) throws
where T: Mock, T: U {
    [...]
}

但编译报错:

Type 'T' constrained to non-protocol, non-class type 'U'

请问有没有办法在编译时实现该约束,替代运行时检查?

解决方案

原因分析

Swift中T: U这种约束要求U必须是协议或类,因为值类型(结构体、枚举)不支持继承,无法作为被约束的父类型。而原代码中U是Factory<U>的泛型参数,编译器无法确定它是协议/类还是值类型,因此报错。

方案1:约束U为类或协议

如果你的U是类或协议(大部分测试场景中,Factory注册的依赖都是协议或类类型),可以在where子句中添加U: AnyObject约束,这样编译器就能确认U是允许被继承/遵循的类型,从而支持T: U的编译时约束:

@MainActor
func registerAndAssignMock<T, U>(_ mockKeyPath: ReferenceWritableKeyPath<Self, T>,
                                 asFactory factoryKeyPath: KeyPath<Container, Factory<U>>) throws
where T: Mock, T: U, U: AnyObject {
    self[keyPath: mockKeyPath] = .init()
    
    // 编译时已确保T符合U,无需运行时检查
    Container.shared[keyPath: factoryKeyPath].register { @MainActor [unowned self] in
        return self[keyPath: mockKeyPath] as U
    }
}

方案2:让T与U类型完全匹配(支持值类型)

如果需要支持U为值类型的场景,由于值类型无法继承,只能要求T和U是同一类型。可以简化泛型参数,直接用单个泛型T:

@MainActor
func registerAndAssignMock<T>(_ mockKeyPath: ReferenceWritableKeyPath<Self, T>,
                              asFactory factoryKeyPath: KeyPath<Container, Factory<T>>) throws
where T: Mock {
    self[keyPath: mockKeyPath] = .init()
    
    Container.shared[keyPath: factoryKeyPath].register { @MainActor [unowned self] in
        return self[keyPath: mockKeyPath]
    }
}

这种方式完全消除了类型不匹配的可能,编译时就能确保类型一致,同时支持值类型和引用类型。

方案3:用协议约束U

如果你的U本身就是协议,可以直接在泛型约束中明确:

@MainActor
func registerAndAssignMock<T, U>(_ mockKeyPath: ReferenceWritableKeyPath<Self, T>,
                                 asFactory factoryKeyPath: KeyPath<Container, Factory<U>>) throws
where T: Mock, T: U, U: Protocol {
    // 实现代码...
}

不过U: Protocol的约束在实际使用中不如U: AnyObject灵活,因为后者同时覆盖类和协议类型。

内容的提问来源于stack exchange,提问作者iSpain17

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 22:53:17