首页 > 解决方案 > 如何将此方法作为函数参数传递,而不是注入整个类

问题描述

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

我的网络层有以下接口

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
}

我的具体类符合HTTPClient并且目前我正在注入这种依赖注入。

但是,这需要我的个人资料模块了解我的网络模块。这会在我要删除的那些模块之间创建耦合。

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

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

我无法完全理解的是如何将HTTPClient界面表示为一个简单的函数。

我相信它是:

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

但是我也不能代表该HTTPClientTask财产。

这应该是一个带有返回 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()

标签: swiftdependency-injectionswift-protocols

解决方案


我相信URLSessionTaskWrapper如果不使用抽象接口(例如HTTPClientTask.

您也许可以返回URLSessionTask本身。

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

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

推荐阅读