Swift 带有关联值的枚举+;仿制药+;具有关联类型的协议
我正在尝试使我的API服务尽可能通用: API服务类Swift 带有关联值的枚举+;仿制药+;具有关联类型的协议,swift,generics,protocols,associated-types,Swift,Generics,Protocols,Associated Types,我正在尝试使我的API服务尽可能通用: API服务类 class ApiService { func send<T>(request: RestRequest) -> T { return request.parse() } } enum RestRequest { case auth(_ request: AuthRequest) case data(_ request: DataRequest) // COMPILER ERROR HERE
class ApiService {
func send<T>(request: RestRequest) -> T {
return request.parse()
}
}
enum RestRequest {
case auth(_ request: AuthRequest)
case data(_ request: DataRequest)
// COMPILER ERROR HERE: Generic parameter 'T' is not used in function signature
func parse<T: Parseable>() -> T.ResponseType {
switch self {
case .auth(let request): return (request as T).parse()
case .data(let request): return (request as T).parse()
}
}
enum AuthRequest: Parseable {
case login(email: String, password: String)
case signupWithFacebook(token: String)
typealias ResponseType = String
func parse() -> ResponseType {
return "String!!!"
}
}
enum DataRequest: Parseable {
case content(id: String?)
case package(id: String?)
typealias ResponseType = Int
func parse() -> ResponseType {
return 16
}
}
}
我试图提出一个解决方案,使用泛型和关联类型的协议以干净的方式处理解析。但是,我在将请求案例与不同的响应类型关联起来时遇到了困难,因为这样做既简单又类型安全:
protocol Parseable {
associatedtype ResponseType
func parse() -> ResponseType
}
端点
class ApiService {
func send<T>(request: RestRequest) -> T {
return request.parse()
}
}
enum RestRequest {
case auth(_ request: AuthRequest)
case data(_ request: DataRequest)
// COMPILER ERROR HERE: Generic parameter 'T' is not used in function signature
func parse<T: Parseable>() -> T.ResponseType {
switch self {
case .auth(let request): return (request as T).parse()
case .data(let request): return (request as T).parse()
}
}
enum AuthRequest: Parseable {
case login(email: String, password: String)
case signupWithFacebook(token: String)
typealias ResponseType = String
func parse() -> ResponseType {
return "String!!!"
}
}
enum DataRequest: Parseable {
case content(id: String?)
case package(id: String?)
typealias ResponseType = Int
func parse() -> ResponseType {
return 16
}
}
}
enum RestRequest{
案例验证(请求:AuthRequest)
案例数据(请求:数据请求)
//此处出现编译器错误:函数签名中未使用泛型参数“T”
func parse()->T.ResponseType{
切换自身{
case.auth(let请求):返回(请求为T.parse()
data(let请求):return(请求为T.parse())
}
}
枚举身份验证请求:可解析{
案例登录(电子邮件:字符串,密码:字符串)
案例signupWithFacebook(令牌:字符串)
typealias ResponseType=字符串
func parse()->ResponseType{
返回“字符串!!!”
}
}
枚举数据请求:可解析{
案例内容(id:字符串?)
案例包(id:字符串?)
typealias ResponseType=Int
func parse()->ResponseType{
返回16
}
}
}
即使我使用T.ResponseType
作为函数返回,如何在函数签名中不使用T
有没有更好的干净方法来实现这一点
我正在尝试使我的API服务尽可能通用:
首先,也是最重要的,这永远不应该是一个目标。相反,您应该从用例开始,并确保您的API服务满足它们。“尽可能地通用”并不意味着什么,只会让你在向事物添加“通用特性”时陷入类型噩梦,这与在许多用例中普遍有用是不同的。哪些呼叫者需要这种灵活性?从呼叫者开始,协议就跟着来了
func send<T>(request: RestRequest) -> T
编译器如何知道stringResponse
应该是字符串?这里没有写“字符串”,所以你必须这样做:
let stringResponse: String = ...
这是非常丑陋的斯威夫特。相反,您可能想要(但不是真的):
“但不是真的”,因为没有办法很好地实现这一点。send
如何知道如何将“我得到的任何响应”转换为“一种碰巧被称为字符串的未知类型?”这会起什么作用
protocol Parseable {
associatedtype ResponseType
func parse() -> ResponseType
}
这种PAT(带有关联类型的协议)实际上没有意义。它说,如果某个实例可以返回ResponseType,那么它是可解析的。但这将是一个解析器,而不是“可以解析的东西”
对于可以解析的东西,您需要一个可以接受一些输入并创建自身的init。最好的方法通常是可编码的,但您可以自己制作,例如:
protocol Parseable {
init(parsing data: Data) throws
}
但我倾向于使用Codable,或者只传递解析函数(见下文)
这可能是enum的一个不好的用法,特别是如果您想要的是通用性的话。每个新的RestRequest都需要更新parse
,这对于此类代码来说是错误的。枚举使添加新的“所有实例实现的东西”变得容易,但添加“新类型的实例”却很困难。结构(+协议)则相反。它们使添加新的协议种类变得容易,但添加新的协议需求却很困难。请求,特别是在一般系统中,是后一种。您希望随时添加新请求。枚举使这很难
有没有更好的干净方法来实现这一点
这取决于“这”是什么。您的呼叫代码是什么样子的?当前系统在何处创建要消除的代码重复?你的用例是什么?没有“尽可能通用”这样的东西,只有一些系统能够适应它们准备处理的轴上的用例。不同的配置轴导致不同类型的多态性,并且具有不同的权衡
您希望您的呼叫代码是什么样子的
不过,为了举例说明这可能是什么样子,应该是这样的
final class ApiService {
let urlSession: URLSession
init(urlSession: URLSession = .shared) {
self.urlSession = urlSession
}
func send<Response: Decodable>(request: URLRequest,
returning: Response.Type,
completion: @escaping (Response?) -> Void) {
urlSession.dataTask(with: request) { (data, response, error) in
if let error = error {
// Log your error
completion(nil)
return
}
if let data = data {
let result = try? JSONDecoder().decode(Response.self, from: data)
// Probably check for nil here and log an error
completion(result)
return
}
// Probably log an error
completion(nil)
}
}
}
final class服务{
让urlSession:urlSession
init(urlSession:urlSession=.shared){
self.urlSession=urlSession
}
func send(请求:URLRequest,
返回:Response.Type,
完成:@转义(响应?->无效){
dataTask(带:请求){(数据、响应、错误)在
如果let error=error{
//记录你的错误
完成(无)
返回
}
如果let data=data{
让result=try?JSONDecoder().decode(Response.self,from:data)
//可能在此处检查nil并记录错误
完成(结果)
返回
}
//可能记录了一个错误
完成(无)
}
}
}
这是非常通用的,可以应用于许多类型的用例(尽管这种特殊形式非常原始)。您可能会发现它并不适用于所有的用例,所以您应该开始扩展它。例如,可能您不喜欢在这里使用可解码。您需要一个更通用的解析器。很好,让解析器可配置:
func send<Response>(request: URLRequest,
returning: Response.Type,
parsedBy: @escaping (Data) -> Response?,
completion: @escaping (Response?) -> Void) {
urlSession.dataTask(with: request) { (data, response, error) in
if let error = error {
// Log your error
completion(nil)
return
}
if let data = data {
let result = parsedBy(data)
// Probably check for nil here and log an error
completion(result)
return
}
// Probably log an error
completion(nil)
}
}
func发送(请求:URLRequest,
返回:Response.Type,
解析方式:@escaping(Data)->Response?,
完成:@转义(响应?->无效){
dataTask(带:请求){(数据、响应、错误)在
如果let error=error{
//记录你的错误
完成(无)
返回
}
如果let data=data{
let result=parsedBy(数据)
//可能在此处检查nil并记录错误
完成(结果)
返回
}
//可能记录了一个错误
公司
enum RestRequest {}
final class ApiService {
let urlSession: URLSession
init(urlSession: URLSession = .shared) {
self.urlSession = urlSession
}
func send<Response: Decodable>(request: URLRequest,
returning: Response.Type,
completion: @escaping (Response?) -> Void) {
urlSession.dataTask(with: request) { (data, response, error) in
if let error = error {
// Log your error
completion(nil)
return
}
if let data = data {
let result = try? JSONDecoder().decode(Response.self, from: data)
// Probably check for nil here and log an error
completion(result)
return
}
// Probably log an error
completion(nil)
}
}
}
func send<Response>(request: URLRequest,
returning: Response.Type,
parsedBy: @escaping (Data) -> Response?,
completion: @escaping (Response?) -> Void) {
urlSession.dataTask(with: request) { (data, response, error) in
if let error = error {
// Log your error
completion(nil)
return
}
if let data = data {
let result = parsedBy(data)
// Probably check for nil here and log an error
completion(result)
return
}
// Probably log an error
completion(nil)
}
}
func send<Response: Decodable>(request: URLRequest,
returning: Response.Type,
completion: @escaping (Response?) -> Void) {
send(request: request,
returning: returning,
parsedBy: { try? JSONDecoder().decode(Response.self, from: $0) },
completion: completion)
}