使用泛型组合两个独立的几乎完全相同的函数

时间:2018-12-02 10:14:23

标签: swift generics

我有2个功能,可用来在一些网络代码上提供一层。

从历史上看,只有1个函数,但是我只是坐下来扩展它,并意识到除了某些类型之外,它们是相同的。

是否可以使用泛型将它们组合在一起?

那是致命的杀手吗?

我已经阅读了一些书,并且正在努力了解如何在此处实现泛型

func profile(with endpoint: ProfilesEndpoint, method: HTTPMethod, body: String?, headers: [String: String]?, completion: @escaping (Either<ProfileResponse>) -> Void) {
    var request = endpoint.request
    request.httpMethod = method.rawValue

    if let body = body {
        request.httpBody = body.data(using: .utf8)
    }

    if let headers = headers {
        for (key, value) in headers {
            request.addValue(value, forHTTPHeaderField: key)
        }
    }

    execute(with: request, completion: completion)
}

func identity(with endpoint: IdentityEndpoint, method: HTTPMethod, body: String?, headers: [String: String]?, completion: @escaping (Either<OAuthTokenResponse>) -> Void) {
    var request = endpoint.request
    request.httpMethod = method.rawValue

    if let body = body {
        request.httpBody = body.data(using: .utf8)
    }

    if let headers = headers {
        for (key, value) in headers {
            request.addValue(value, forHTTPHeaderField: key)
        }
    }

    execute(with: request, completion: completion)
}

我已经尝试过,但是收到一个错误Value of type 'T' has no member 'request'

func request<T: Comparable, X: Comparable>(with endpoint: T, method: HTTPMethod, body: String?, headers: [String: String]?, completion: @escaping (Either<X>) -> Void) {
    var request = endpoint.request
    request.httpMethod = method.rawValue

    if let body = body {
        request.httpBody = body.data(using: .utf8)
    }

    if let headers = headers {
        for (key, value) in headers {
            request.addValue(value, forHTTPHeaderField: key)
        }
    }

    execute(with: request, completion: completion)
}

3 个答案:

答案 0 :(得分:0)

要找到合适的解决方案并不容易,因为这不是我可以直接将粘贴复制到操场上然后玩的东西。无论如何,我希望这会起作用:

protocol Endpoint {
    var request: URLRequest { get } 
}

extension IdentityEndpoint: Endpoint {}
extension ProfilesEndpoint: Endpoint {}

func genericFunc<T>(with endpoint: Endpoint, method: HTTPMethod, body: String?: headers: [String: String]?, completion: @escaping (Either<T>) -> Void) {
    // You can repeat the same stuff here
}

答案 1 :(得分:0)

使用泛型将两个函数替换为一个函数是可能的,也是一个好主意。为此,您首先需要定义一个具有端点通用功能的协议。如果我没看错,它应该看起来像这样:

protocol Endpoint {
    associatedtype Response

    var request: URLRequest { get }
}

也许您需要向Response关联类型添加一些约束。这取决于您的execute函数。

然后,您可以像这样定义通用request函数:

func request<E: Endpoint>(with endpoint: E, method: HTTPMethod, body: String?, headers: [String: String]?, completion: @escaping (Either<E.Response>) -> Void) { ... }

答案 2 :(得分:0)

您收到该错误,是因为并非所有类型都具有名为request的变量。泛型不适合这个问题,因为您指的是特定的类(不通用)。

以下两个想法可以解决您的问题:

方法1-

我认为这是最好的选择,因为它很直观-每个函数都有其自己的用途和描述,而代码可以回收并保持简单

更改

func request<T: Comparable, X: Comparable>(with endpoint: T, method: HTTPMethod, body: String?, headers: [String: String]?, completion: @escaping (Either<X>) -> Void)

private func request(urlRequest: URLRequest, method: HTTPMethod, body: String? , headers: [String: String]?, completion: @escaping (response: HttpResponse) -> Void)

然后从profile(...)identity(...)中调用您的请求函数-

func profile(with endpoint: ProfilesEndpoint, method: HTTPMethod, body: String?, headers: [String: String]?, completion: @escaping (Either<ProfileResponse>) -> Void) {
    var urlRequest = endpoint.request
    // call request(urlRequest: urlRequest, ...) and handle completion
}

func identity(with endpoint: IdentityEndpoint, method: HTTPMethod, body: String?, headers: [String: String]?, completion: @escaping (Either< OAuthTokenResponse>) -> Void) {
    var urlRequest = endpoint.request
    // call request(urlRequest: urlRequest, ...) and handle completion 
}

private func request(urlRequest: URLRequest, method: HTTPMethod, body: String? , headers: [String: String]?, completion: @escaping (response: HttpResponse) -> Void){ 

    var request = urlRequest
    .
    .
    .
 }

方法2-

创建一个名为Endpoint的协议,并在ProfileEndpoint和IdentityEndpoint上实现它。

protocol Endpoint{
    var request: URLRequest { get }
}

class ProfilesEndpoint: Endpoint {
    // this class must contain variable request because it implements Endpoint protocol
    var request: URLRequest{
        .
        .
        return ...
    }
}
class IdentityEndpoint: Endpoint {
    // this class must contain variable request because it implements Endpoint protocol
    var request: URLRequest{
        .
        .
        return ...
    }
}

然后您可以将请求功能更改为-

(我对Either<>确实不熟悉,所以不确定您是否会完成)

func request(with endpoint: Endpoint, method: HTTPMethod, body: String?, headers: [String: String]?, completion: @escaping (Either<X>) -> Void) {

    var request = endpoint.request
    request.httpMethod = method.rawValue

    if let body = body {
        request.httpBody = body.data(using: .utf8)
    }

    if let headers = headers {
        for (key, value) in headers {
            request.addValue(value, forHTTPHeaderField: key)
        }
    }

    execute(with: request, completion: completion)
}

您可以使用IdentityEndpointProfilesEndpoint调用此函数,因为它们都符合protocol Endpoint-声明了变量request的变量,因此可以肯定的是,这两个类都将具有此变量