Swift 带有关联值的枚举+;仿制药+;具有关联类型的协议

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

我正在尝试使我的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: 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)
}