如何将此方法作为函数参数传递而不是注入整个 class

How can I pass this method as a function argument rather than inject the entire class

我创建了一个包含我的网络代码的项目。我有另一个项目,其中包含一些与用户配置文件相关的服务和视图控制器。

我的网络层有如下接口

public protocol HTTPClientTask {
  func cancel()
}

public typealias HTTPClientResult = Result<(response: HTTPURLResponse, data: Data), Error>

public protocol HTTPClient {
  /// The completion handler can be invoked in any thread.
  /// Clients are responsible for dispatching to the appropriate thread, if needed.
  @discardableResult
  func execute(_ request: URLRequest, completion: @escaping (HTTPClientResult) -> Void) -> HTTPClientTask
}

我的具体 class 符合 HTTPClient,目前我正在注入这个依赖注入。

然而,这需要我的配置文件模块知道我的网络模块。这在我想删除的那些模块之间创建了耦合。

由于 HTTPClient 只有一个方法 returns 一个任务,我想我也许可以更新我的配置文件模块以接受函数签名而不是客户端。

这样只有我的应用程序中的组合根才会知道这两个模块,我认为这是可以接受的。

我不太明白的是如何将 HTTPClient 接口表示为一个简单的函数。

我认为是:

typealias ClientResult = Result<(response: HTTPURLResponse, data: Data), Error>
typealias ClientMethod = (URLRequest, @escaping (ClientResult) -> Void) -> HTTPClientTask

不过我也不能代表HTTPClientTask属性。

这应该是一个在 returns void 上有方法的对象。

本质上,我想将 execute 方法作为参数传递,但无法在不使用当前协议的情况下将其表示为一种类型。

可以在下面找到有关如何实现此接口的示例:

public final class URLSessionHTTPClient: HTTPClient {

  private let session: URLSession
  private struct UnexpectedValuesRepresentationError: Error { }
  private struct URLSessionTaskWrapper: HTTPClientTask {
    let wrapped: URLSessionTask
    func cancel() {
      wrapped.cancel()
    }
  }

  public init(session: URLSession = .shared) {
    self.session = session
  }

  public func execute(_ request: URLRequest, completion: @escaping (HTTPClientResult) -> Void) -> HTTPClientTask {
    let task = session.dataTask(with: request) { data, response, error in
      completion(Result {
        if let error = error {
          throw error
        } else if let data = data, let response = response as? HTTPURLResponse {
          return (response, data)
        } else {
          throw UnexpectedValuesRepresentationError()
        }
      })
    }

    task.resume()
    return URLSessionTaskWrapper(wrapped: task)
  }
}

我在下面添加了一个游乐场,希望能更清楚。

import UIKit

public protocol HTTPClientTask {
  func cancel()
}

public typealias HTTPClientResult = Result<(response: HTTPURLResponse, data: Data), Error>

public protocol HTTPClient {
  /// The completion handler can be invoked in any thread.
  /// Clients are responsible for dispatching to the appropriate thread, if needed.
  @discardableResult
  func execute(_ request: URLRequest, completion: @escaping (HTTPClientResult) -> Void) -> HTTPClientTask
}


public final class URLSessionHTTPClient: HTTPClient {

  private let session: URLSession
  private struct UnexpectedValuesRepresentationError: Error { }
  private struct URLSessionTaskWrapper: HTTPClientTask {
    let wrapped: URLSessionTask
    func cancel() {
      wrapped.cancel()
    }
  }

  public init(session: URLSession = .shared) {
    self.session = session
  }

  public func execute(_ request: URLRequest, completion: @escaping (HTTPClientResult) -> Void) -> HTTPClientTask {
    let task = session.dataTask(with: request) { data, response, error in
      completion(Result {
        if let error = error {
          throw error
        } else if let data = data, let response = response as? HTTPURLResponse {
          return (response, data)
        } else {
          throw UnexpectedValuesRepresentationError()
        }
      })
    }

    task.resume()
    return URLSessionTaskWrapper(wrapped: task)
  }
}

let client = URLSessionHTTPClient(session: .init(configuration: .ephemeral))

typealias ClientResult = Result<(response: HTTPURLResponse, data: Data), Error>
typealias ClientMethod = (URLRequest, @escaping (ClientResult) -> Void) -> HTTPClientTask // I want to remove the knowledge of `HTTPClientTask` from the typealias

class Loader {

  private let load: ClientMethod

  init(load: @escaping ClientMethod) {
    self.load = load
  }

  func get() {
    let url = URL(string: "https://google.com")!
    load(.init(url: url)) { result in
      print(result)
    }
  }
}


let loader = Loader(load: client.execute(_:completion:))

loader.get()

我相信 URLSessionTaskWrapper 可能很难在不使用诸如 HTTPClientTask 之类的抽象接口的情况下表示这一点。

您也许可以 return URLSessionTask 本身。

public protocol HTTPClient {
  @discardableResult
  func execute(_ request: URLRequest, completion: @escaping (HTTPClientResult) -> Void) -> URLSessionTask
}

...
typealias ClientMethod = (URLRequest, @escaping (ClientResult) -> Void) -> URLSessionTask