Утечка памяти Swift Combine из CFNetwork - PullRequest
1 голос
/ 20 июня 2020

Я пытаюсь заткнуть утечку памяти. У меня есть следующий класс, который получает запросы API:

public struct Service {

  public let baseURL: URL
  public let session: URLSession

  public init (baseURL: URL, session: URLSession) {
    self.baseURL = baseURL
    self.session = session
  }

  public struct Response {
    public let data: Data
    public let response: URLResponse
  }



  public enum ServiceError: Error {
    case api(title: String, messages: [String])
    case other(Error)
  }



  struct ServiceErrorResponse: Decodable {
    let response: ErrorResponse

    enum CodingKeys: String, CodingKey {
      case response = "error"
    }
  }



  struct ErrorResponse: Decodable {
    let title: String
    let messages: [String]
  }



  public enum HTTPMethod: String {
    case get = "GET"
    case put = "PUT"
    case post = "POST"
    case patch = "PATCH"
    case delete = "DELETE"
  }



  public func run(_ request: URLRequest) -> AnyPublisher<Response, ServiceError> {
    return session
      .dataTaskPublisher(for: request)
      .tryMap { data, response in
        guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
          let error = try JSONDecoder().decode(ServiceErrorResponse.self, from: data)
          let title = error.response.title
          let messages = error.response.messages

          print(error.response)

          throw ServiceError.api(title: title, messages: messages)
        }

        return Response(data: data, response: response)
      }
      .mapError { err in
        let error = err is ServiceError ? err : ServiceError.other(err)
        return error as! Service.ServiceError
      }
      .eraseToAnyPublisher()
  }



  public func fetch(
    _ path: String,
    method: HTTPMethod = .get,
    params: Data? = nil
  ) -> AnyPublisher<Response, ServiceError> {

    let url: URL

    if let params = params, method == .get {
      url = buildGetURLWithParams(path: path, params: params)!
    }
    else {
      url = baseURL.appendingPathComponent(path)
    }


    var request = URLRequest(url: url)
    request.httpMethod = method.rawValue
    request.addValue("application/json", forHTTPHeaderField: "Content-Type")


    if let params = params, method != .get {
      request.httpBody = params
    }

    return run(request)
  }


  private func buildGetURLWithParams(path: String, params: Data) -> URL? {
    if let json = try? JSONSerialization.jsonObject(with: params, options: []) as? [String: String] {
      var urlComponents = URLComponents(
        url: baseURL.appendingPathComponent(path),
        resolvingAgainstBaseURL: false
      )
      urlComponents?.queryItems = json.map { URLQueryItem(name: $0, value: $1) }

      return urlComponents?.url
    }
    else { return nil }
  }
}

Затем я делаю запросы из приложения, используя следующее:

typealias ServiceResponse = Service.Response
typealias ServiceError = Service.ServiceError
typealias ServiceMethod = Service.HTTPMethod

    enum MyAPI {

      static let service = Service(
        baseURL: URL(string: "http://127.0.0.1:3000/api")!,
        session: URLSession(configuration: URLSessionConfiguration.default)
  )

  static func login(email: String, password: String) -> AnyPublisher<ServiceResponse, ServiceError> {
    let params = ["email": email, "password": password]
    let json = try! JSONEncoder().encode(params)

    return service.fetch("/login", method: .post, params: json)
  }
}

Функция login получает ответ и возвращает AnyPublisher, который потребляется следующим образом:

enum UserAction {
  case login
  case loginSuccess(UserResponse)
  case loginFailure
  case logout



  static func login(email: String, password: String) -> Dispatch<AppAction> {
    return { dispatch in
      dispatch(.userAction(action: .login))


      return MyAPI.login(email: email, password: password)
        .map(\.data)
        .decode(type: UserResponse.self, decoder: JSONDecoder())
        .receive(on: DispatchQueue.main)
        .sink(
          receiveCompletion: { completion in
            if case .failure(let err) = completion {
              print("--------------------------")
              print("Retrieving data failed with error \(err)")
            }
          },
          receiveValue: { result in
            dispatch(.userAction(action: .loginSuccess(result))) // Here I have a memory leak

          }
      )
    }
  }
}

Я имитирую что-то вроде Redux, в котором действия отправки изменяют состояние, поэтому я возвращаю «эффект» из входа в систему UserAction, который получает функцию отправки. Все работает нормально, но в строке receiveValue я получаю утечку памяти со следующим описанием:

enter image description here

Любые идеи, что может быть причиной этого или как Я могу узнать? Я новичок в Xcode и Swift.

...