创建严格的通用类型“自我”的属性

时间:2019-04-12 01:11:03

标签: swift generics

我想在一个使用类类型作为通用参数的类上创建一个属性,但是很难解决。

open class ResponseProcessor {

  required public init() {

  }

  var success: ((_ responseProcessor: ResponseProcessor) -> Void)?

  func process() {

    success?(self)

  }

}

class TestProcessor: ResponseProcessor {

  var result: String?

  override func process() {

    result = "Some Result"

    super.process()

  }

}

open class Request<ResponseProcessorType: ResponseProcessor> {

  var success: ((_ responseProcessor: ResponseProcessor) -> Void)?

  func doRequest() {

    let responseProcessor = ResponseProcessorType.init()
    responseProcessor.success = success
    responseProcessor.process()

  }

}

class TestRequest: Request<TestProcessor> {

}

let testRequest = TestRequest()
testRequest.success = { (responseProcessor) in
  // This line reports an error, but I want it to know what 
  // type the responseProcessor is.
  print(responseProcessor.result) 

}
testRequest.doRequest()

我希望能够将SubRequest分配给.request变量,但是由于严格的通用类型而不能。

因此,我想说“ request上的ResponseProcessor属性应为Request<WhateverThisClassIs>类型,但我不知道该如何表达,或以一种可行的方式进行声明。

应该知道testProcessor.request的类型为HTTPRequest<TestProcessor>,但显然没有发生。

1 个答案:

答案 0 :(得分:1)

我不确定这是否会回答您的问题,但是也许它将使您走上更好的道路。对于您提出的问题,答案是Swift中没有通用协方差。您试图写的东西是不可能的。通用协方差实际上并不能修复您的代码,因为这里还有很多其他类型问题(您的最新版本可能违反了Liskov的Substitution Principle,这意味着它打破了类继承的含义)。但我认为您根本不想要自己想要写的东西。

我怀疑您正在编写可插拔且可测试的网络堆栈。这真的很常见。他是一个很简单的人。如果您再拆开一点,它们可以变得更强大。

首先,低级网络堆栈本身应使用URLRequests并返回Data。就这样。它不应尝试处理模型类型。人们总是在这里脱轨。因此,请求是一个URLRequest和一个完成处理程序:

struct Request {
    let urlRequest: URLRequest
    let completion: (Result<Data, Error>) -> Void
}

然后,客户将其消耗掉。

final class NetworkClient {
    func fetch(_ request: Request) {
        URLSession.shared.dataTask(with: request.urlRequest) { (data, _, error) in
            if let error = error { request.completion(.failure(error)) }
            else if let data = data { request.completion(.success(data)) }
            }.resume()
    }
}

现在,我们通常不希望在测试时与URLSession交谈。我们可能想放弃预先罐装的数据。所以我们做其中之一。

final class TestClient {
    enum ClientError: Error {
        case underflow
    }
    var responses: [Result<Data, Error>]
    init(responses: [Result<Data, Error>]) { self.responses = responses }
    func fetch(_ request: Request) {
        if let response = responses.first {
            responses.removeFirst()
            request.completion(response)
        } else {
            request.completion(.failure(ClientError.underflow))
        }
    }
}

我正在标记事物final class,因为它们是明智的引用类型,但是我想说明一下,我在这里没有使用类继承。 (请随意在您自己的代码中保留“ final”;这有点花哨,通常是不需要的。)

这两件事有何相似之处?他们共享一个协议:

protocol Client {
    func fetch(_ request: Request)
}

太好了。现在我可以做类似的事情:

let client: Client = TestClient(responses: [])

没有关联的类型意味着Client作为类型是完全可以的。

但是取回数据有点丑陋。我们需要一种类型,例如User。

struct User: Codable, Equatable {
    let id: Int
    let name: String
}

我们该怎么做?我们只需要一种构造可获取可解码内容的请求的方法:

extension Request {
    init<Model: Decodable>(fetching: Model.Type,
                           from url: URL,
                           completion: @escaping (Result<Model, Error>) -> Void) {
        self.urlRequest = URLRequest(url: url)
        self.completion = { data in
            completion(Result {
                try JSONDecoder().decode(Model.self, from: data.get())})
        }
    }
}

请注意,Request是否仍然对模型一无所知?客户对模型一无所知。这个Request初始化程序只有一个Model类型,并以可以接受Data并吐回Model的方式包装它。

您可以进一步采用这种方法。您可以编写一个用于包装客户端并修改请求的客户端,例如添加标头。

struct AddHeaders: Client {
    let base: Client
    let headers: [String: String]

    func fetch(_ request: Request) {
        var urlRequest = request.urlRequest
        for (key, value) in headers {
            urlRequest.addValue(value, forHTTPHeaderField: key)
        }

        base.fetch(Request(urlRequest: urlRequest,
                           completion: request.completion))
    }
}

let client = AddHeaders(base: NetworkClient(),
                        headers: ["Authorization": "Token ...."])

这里没有子类,没有泛型,只有一种协议(没有关联的类型)和一种泛型方法。但是您可以插入各种各样的后端,并将可以进行匹配少数转换之一的任何操作组合在一起(请求->请求,请求->数据,数据->无效)。

我希望这与您对问题的理解相符。祝你好运。