使用更通用类型的系统类的协议一致性

时间:2018-03-15 11:25:07

标签: swift

为了模拟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$),这有用,但它有点混乱。

1 个答案:

答案 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)
相关问题