我写了一个简单的fetch客户端来处理请求。代码如下:
申请
public protocol RequestParameter: Encodable {
var queryItems: [URLQueryItem] { get }
}
public extension RequestParameter {
var queryItems: [URLQueryItem] {
var this = [URLQueryItem]()
let mirror = Mirror(reflecting: self)
for child in mirror.children {
guard let label = child.label, let value = child.value as? CustomStringConvertible else {
continue
}
this.append(.init(name: label, value: value.description))
}
return this
}
}
extension Dictionary: RequestParameter where Key == String, Value: CustomStringConvertible & Encodable {
public var queryItems: [URLQueryItem] {
var this = [URLQueryItem]()
for (key, value) in self {
this.append(.init(name: key, value: value.description))
}
return this
}
}
public protocol Request<Parameter, Response> {
associatedtype Parameter: RequestParameter
associatedtype Response: Decodable
var method: HTTPMethod { get }
var path: String { get }
var parameter: Parameter? { get }
var headers: HTTPHeaders? { get }
}
public extension Request {
func build(_ urlString: String, encoder: JSONEncoder? = nil) -> URLRequest {
var components = URLComponents(string: urlString)
components?.path = path
if method == .get {
components?.queryItems = parameter?.queryItems
}
guard let url = components?.url else {
fatalError("url can't be nil")
}
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = method.rawValue
if let headers {
urlRequest.headers = headers
}
if method == .post, let parameter, let encoder {
do {
urlRequest.httpBody = try encoder.encode(parameter)
} catch {
fatalError("`parameter cannot be encoded`")
}
}
return urlRequest
}
}
客户端
public protocol Client {
func send<RequestType: Request>(_ request: RequestType, encoder: JSONEncoder?, decoder: JSONDecoder) async throws -> RequestType.Response
var urlString: String { get }
static var shared: Self { get }
}
public extension Client {
func send<RequestType: Request>(_ request: RequestType, encoder: JSONEncoder? = nil, decoder: JSONDecoder = .init()) async throws -> RequestType.Response {
let (data, urlResponse) = try await URLSession.shared.data(for: request.build(urlString, encoder: encoder))
guard let httpResponse = urlResponse as? HTTPURLResponse else {
throw #InvalidResponse
}
guard 200 ... 299 ~= httpResponse.statusCode else {
throw #NetworkFailure(httpResponse.statusCode)
}
return try decoder.decode(RequestType.Response.self, from: data)
}
}
因此,一个简单而有效的网络请求代码看起来像这样:
struct FooRequest: Request {
typealias Parameter = FooParameter
typealias Response = Foo
var method: HTTPMethod = .get
var path: String = ""
var parameter: FooParameter? = FooParameter()
var headers: HTTPHeaders? = nil
}
struct FooParameter: RequestParameter {}
struct Foo: Decodable {}
struct FooClient: Client {
var urlString: String = "fooURLString.com"
static var shared: FooClient = Self()
}
let result = try await FooClient.shared.send(FooRequest())
但是有时候一个简单的GET请求可能不需要参数。为了避免重复代码var parameter: FooParameter? = nil
或var parameter: BarParameter? = nil
,我添加了一个NeverParameter
类型:
public struct NeverParameter: RequestParameter {
private init() {}
}
public extension Request where Parameter == NeverParameter {
var parameter: Parameter? { nil }
}
这个时候,问题来了。我认为使用NeverParameter
,请求应该是这样的:
struct FooRequest: Request {
typealias Parameter = NeverParameter
typealias Response = Foo
var method: HTTPMethod = .get
var path: String = ""
var headers: HTTPHeaders? = nil
}
但实际上,注解掉typealias Parameter = NeverParameter
仍然可以编译:
struct FooRequest: Request {
// typealias Parameter = NeverParameter
typealias Response = Foo
var method: HTTPMethod = .get
var path: String = ""
var headers: HTTPHeaders? = nil
}
(lldb)po类型(的:参数)
Swift.Optional<Request.NeverParameter>
为什么编译器可以推断出参数的类型是NeverParameter
?即使默认值nil只添加在NeverParameter
的where子句下?再添加一个具有相同实现的NeverParameter1
,Xcode最终抛出了一个错误。
public struct NeverParameter: RequestParameter {
private init() {}
}
public extension Request where Parameter == NeverParameter {
var parameter: Parameter? { nil }
}
public struct NeverParameter1: RequestParameter {
private init() {}
}
public extension Request where Parameter == NeverParameter1 {
var parameter: Parameter? { nil }
}
错误:
类型“FooRequest”不符合协议“Request”
1条答案
按热度按时间dsekswqp1#
关联类型推理在语言中已经存在很长时间了。琐碎的情况是:
这不一定是编译器的实现方式,但你可以想象编译器会想:
Bar
没有T
类型。methodThatReturnsT
应该返回T
。Bar.methodThatReturnsT
返回一个Int
,所以T
必须是Int
。你在这里的情况基本上是一样的,只是有点不那么直观,因为它涉及到一个条件扩展。这种行为is found to be rather "magical" by some people,甚至reported as a bug。这是*************************。
这不一定是编译器的实现方式,但你可以想象编译器会想:
FooRequest
没有parameter
的声明。但是它是通过扩展名extension Request where Parameter == NeverParameter
实现的。让我们使用这里声明的parameter
。FooRequest
也没有Parameter
类型,但是parameter
应该是Parameter
类型。FooRequest.parameter
(来自扩展名)的类型是NeverParameter
,所以T
必须是NeverParameter
。这种关联的类型推断使得实现一些标准库协议比其他方式容易得多。例如,考虑
OptionSet
的典型实现。这里涉及哪些关联类型?
OptionSet
继承自RawRepresentable
和SetAlgebra
,而SetAlgebra
又继承自ExpressibleByArrayLiteral
,因此有:RawValue
Element
ArrayLiteralElement
RawValue
是根据声明的rawValue
属性推断出来的,这很直观。OptionSet
将Self
声明为Element
的默认值,所以这没有问题。ArrayLiteralElement
是如何被推断出来的?没有声明init(arrayLiteral:)
初始化器,因此不能从中推断。这与
FooRequest
的情况相同-SetAlgebra
的 * 条件扩展 * 帮助推断ArrayLiteralElement
与Element
相同,并且该扩展为init(arrayLiteral:)
提供了默认实现。请参阅Swift源代码中的扩展声明,或参阅此处的默认实现文档。