我想在一个使用类类型作为通用参数的类上创建一个属性,但是很难解决。
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>
,但显然没有发生。
答案 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 ...."])
这里没有子类,没有泛型,只有一种协议(没有关联的类型)和一种泛型方法。但是您可以插入各种各样的后端,并将可以进行匹配少数转换之一的任何操作组合在一起(请求->请求,请求->数据,数据->无效)。
我希望这与您对问题的理解相符。祝你好运。