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
相关产品推荐
相关产品推荐

