为了模拟Swift中的对象进行测试,我通常遵循一种创建协议的模式,该协议描述了我喜欢的对象的行为,然后使用Cuckoo为它生成模拟进行测试。
通常,这些协议直接映射到现有类型,这很好,直到我需要使现有类型与我的新协议类型一起工作。
public typealias RequestCompletionHandler = (Request, Error?) -> Swift.Void
public protocol Request {
var results: [Any]? { get }
var completionHandler: RequestCompletionHandler? { get }
}
extension VNRequest: Request {}
此处,VNRequest
已有一个名为completionHandler
的成员,它返回以下类型:
public typealias VNRequestCompletionHandler = (VNRequest, Error?) -> Swift.Void
从技术上讲,所有这些类型都应该匹配,但显然对于类型求解器来说,它不是一个非常容易解决的场景,所以编译器对此并不太开心。
起初我以为我可以通过执行以下操作来引用原始的completionBlock实现:
extension VNRequest: Request {
public var completionHandler: RequestCompletionHandler? {
return (self as VNRequest).completionHandler
}
}
但它对此也不太满意。
有关如何做到最好的建议?我已经考虑过在协议中使用不同的名称(例如:completionBlock_
或completionBlock$
),这有用,但它有点混乱。
答案 0 :(得分:0)
问题的出现是因为Swift在闭包返回类型方面是协变的,而在其参数方面则是反变量。这意味着(VNRequest, Error?) -> Void
无法在需要(Request, Error?) -> Void
的地方使用(反过来也可以)。
您可以使用Request
协议中的关联类型来解决您的问题:
public protocol Request {
associatedtype RequestType = Self
var results: [Any]? { get }
var completionHandler: ((RequestType, Error?) -> Void)? { get }
}
上述协议定义将使VNRequest
类编译,因为编译器将检测RequestType
的匹配。
但缺点是,具有相关类型的协议在可以使用的位置有一些限制,并且将它们作为函数参数传递将需要一些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)