为了在 Swift 中模拟对象进行测试,我通常遵循这样的模式:编写一个协议(protocol)来描述我想要的对象的行为,然后使用 Cuckoo 为其生成模拟以进行测试。
通常,这些协议(protocol)直接映射到现有类型,这工作得很好,直到我需要使现有类型与我的新协议(protocol)类型一起使用。
public typealias RequestCompletionHandler = (Request, Error?) -> Swift.Void
public protocol Request {
var results: [Any]? { get }
var completionHandler: RequestCompletionHandler? { get }
}
extension VNRequest: Request {}
这里,VNRequest
已经有一个名为 completionHandler
的成员,它返回以下类型:
公共(public)类型别名 VNRequestCompletionHandler = (VNRequest, Error?) -> Swift.Void
从技术上讲,所有这些类型都应该匹配,但显然这对于类型求解器来说并不是一个很容易解决的场景,因此编译器对此不太满意。
起初我以为我可以通过执行以下操作来引用原始的completionBlock实现:
extension VNRequest: Request {
public var completionHandler: RequestCompletionHandler? {
return (self as VNRequest).completionHandler
}
}
但它对此也不太高兴。
关于如何最好地做到这一点有什么建议吗?我考虑过在协议(protocol)中使用不同的名称(例如:completionBlock_
或 completionBlock$
),这可行,但有点杂乱。
最佳答案
出现该问题的原因是 Swift 在闭包返回类型方面是协变的,而在其参数方面是逆变的。这意味着 (VNRequest, Error?) -> Void
不能用在需要 (Request, Error?) -> Void
的地方(反之亦然) )。
您可以通过使用 Request
协议(protocol)中的关联类型来解决您的问题:
public protocol Request {
associatedtype RequestType = Self
var results: [Any]? { get }
var completionHandler: ((RequestType, Error?) -> Void)? { get }
}
上述协议(protocol)定义将使 VNRequest
类进行编译,因为编译器将检测 RequestType
的匹配。
不过,缺点是具有关联类型的协议(protocol)在使用位置方面存在一些限制,并且将它们作为函数参数传递将需要一些 where
子句才能使它们起作用。
另一种选择是使用 Self
作为完成处理程序的参数:
public typealias RequestCompletionHandler<T> = (T, Error?) -> Swift.Void
public protocol Request {
var results: [Any]? { get }
var completionHandler: RequestCompletionHandler<Self>? { get }
}
这也将解决一致性问题,但也有一些限制:VNRequest
必须是最终的,使用 Request
的函数必须是通用的:
func send<R: Request>(_ request: R)
关于swift - 使用更通用类型的系统类的协议(protocol)一致性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49298212/