如何从 Swift 通用函数中捕获参数

How to capture arguments from Swift generic function

我有一个 Swift 协议,它定义了一个用于发出网络请求的通用函数。它看起来像这样:

protocol TaskManagerProtocol {
    func run<V>(
        task: Task<V>,
        completion: @escaping (Result<V, Error>) -> Void
    )
}

这在生产代码中非常有效,让我可以发出接收不同类型结果的请求。到目前为止,还不错。

问题:对于单元测试,我想要一个 Test Spy 来捕获参数,尤其是闭包。间谍看起来像这样:

class TaskManagerSpy<Value>: TaskManagerProtocol {
    var callCount = 0
    var task: [Task<Value>] = []
    var completion: [(Result<Value, Error>) -> Void] = []

    func run<V>(
        task: Task<V>,
        completion: @escaping (Result<V, Error>) -> Void
    ) {
        guard V.self == Value.self else {
            fatalError("run<V> doesn't match init<Value>")
        }
        callCount += 1
        self.task.append(task)
        self.completion.append(completion)
    }
}

我的意图是让测试代码实例化 TaskManagerSpy<SomeType> 并将其注入到调用 run<V>(task:completion:) 的被测系统中。但就目前而言,append() 调用无法编译:

Cannot convert value of type 'Task<V>' to expected argument type 'Task<Value>'

闭包也一样。如果我注释掉这些行,测试就会成功而不会触发致命错误。

问题:我已经证明了类型是一样的。有没有办法将一种类型强制转换为另一种类型,以便我可以捕获参数?

因为正如你所说,你已经断言 V.self == Value.self,你应该能够强制转换 task:

self.task.append(task as! Task<Value>)

completion 也一样:

self.completion.append(completion as! ((Result<Value, Error>) -> Void))

您可以通过在 TaskManagerProtocol 中使用 associatedtype 而不是在 func run 上使用通用类型 V 来执行此操作:

protocol TaskManagerProtocol {
    associatedtype ValueType
    func run(
        task: Task<ValueType>,
        completion: @escaping (Result<ValueType, Error>) -> Void
    )
}

class TaskManagerSpy<Value>: TaskManagerProtocol {
    var callCount = 0
    var task: [Task<Value>] = []
    var completion: [(Result<Value, Error>) -> Void] = []

    func run(
        task: Task<Value>,
        completion: @escaping (Result<Value, Error>) -> Void
    ) {
        callCount += 1
        self.task.append(task)
        self.completion.append(completion)
    }
}