IOS Обработка нескольких асинхронных обратных вызовов с помощью объединения

Я столкнулся с проблемой в моей реализации кэширования в памяти, из-за которой fetchValue не захватывает второй обратный вызов. Моя текущая настройка включает получение данных с использованием Apollo GraphQL с Future от объединения, но второй обратный вызов не запускается, как ожидалось.

func myRequest<T, Q: GraphQLQuery>(query: Q) -> Future<Response<T>, CYGErrorType>? where T: RootSelectionSet {
    return Future<Response<T>, CYGErrorType> { promise in
        guard let futureFetchValue = self.fetchValue(query: query, cachePolicy: self.model.cachePolicy) as Future<Response<T>, CYGErrorType>? else {
            promise(.failure(.default))
            return
        }
        
        futureFetchValue
            .sink(receiveCompletion: { completion in
                switch completion {
                case .finished:
                    break
                case .failure(let error):
                    promise(.failure(error))
                }
            }, receiveValue: { result in
                print("API>> API>> response")
                promise(.success(result))
            })
            .store(in: &self.cancellable)
    }
}

==============

private func fetchValue<T, Query: GraphQLQuery>(query: Query, cachePolicy: CachePolicy) -> Future<Response<T>, CYGErrorType>? where T: RootSelectionSet {
    return Future<Response<T>, CYGErrorType> { promise in
        print("API>> hit")
        apolloClient.fetch(query: query, cachePolicy: cachePolicy) { result in
            switch result {
            case .success(let graphQLResult):
               
                let validateResponse = graphQLResult.isValid(query: query)
                switch validateResponse {
                case .success:
                    guard let data = graphQLResult.data as? T else {
                        promise(.failure(CYGErrorType.default))
                        return
                    }
                    
                    let response = Response(value: data, response: nil, error: nil)
                    promise(.success(response))
                    print("API>> response")

                    
                case .failureWithError(let error):
                    promise(.failure(error))
                }
            case .failure(let error):
                let cygError = self.graphQLErrorParsing(error: error, queryName: Query.operationName)
                promise(.failure(cygError))
            }
        }
    }
}

выход:

print("API>> hit")
 print("API>> response")
 print("API>> API>> response")
 print("API>> response")

Ожидаемый результат

print("API>> hit")
 print("API>> response")
 print("API>> API>> response")
 print("API>> response")
 print("API>> API>> response")

Второй обратный вызов из fetchValue не захватывается из myRequest. Я ожидаю увидеть в выводе оба ответа API, но второй отсутствует. Кажется, что CombineFuture поддерживает только первый обратный вызов, а не второй, но я не уверен, как с этим правильно справиться.

Почему fetchValue не захватывает второй обратный вызов? Как я могу гарантировать, что оба обратных вызова правильно захватываются и обрабатываются? Будем очень признательны за любую помощь или предложения по решению этой проблемы!


51
1

Ответ:

Решено

Ваш код тесно связан с библиотеками (клиент Apollo), с которыми люди, отвечающие на ваш вопрос, могут быть не знакомы, а для тестовой среды потребуется сервер GraphQL, которого у них нет, поэтому напрямую ответить на ваш вопрос будет сложно.

В приведенном вами примере функция myRequest вызывает fetchValue. fetchValue возвращает Future, а myRequest, кажется, оборачивает это Future в другое Future, которое на самом деле не служит никакой цели, кроме повторения результатов 'fetchValue`.

Я не понимаю, почему ты это сделал. Действительно кажется, что myRequest и fetchValue эквивалентны — вы сможете вообще удалить myRequest и просто вызвать fetchValue.

Вы подразумеваете, что apolloClient.fetch может вызывать функцию обратного вызова завершения более одного раза для данного запроса. Так ли это?

Если да, то, как предложено в комментариях, вы можете использовать PassthroughSubject (представляющий тип возвращаемого значения как AnyPublisher).

Future представляет один запрос, который возвращает один результат в какой-то момент в будущем. Это поток, который выдает одно значение, а затем завершается. Поэтому модель apolloClient.fetch будет не очень хорошей, если она будет возвращать более одного результата.

PassthroughSubject — это общий поток, который может содержать несколько значений. Он завершится при возникновении ошибки или при явном завершении потока. (Из вашего кода неясно, как определить, когда apolloClient.fetch завершает отправку значений и поток должен завершиться)

Не обращая внимания на эту деталь, вам может понадобиться что-то вроде этого:

private func fetchValue<T, Query: GraphQLQuery>(query: Query, cachePolicy: CachePolicy) -> AnyPublisher<Response<T>, CYGErrorType>? where T: RootSelectionSet {

  let resultSubject = PassthroughSubject<Response<T>, CYGErrorType>()
  print("API>> hit")
  apolloClient.fetch(query: query, cachePolicy: cachePolicy) { result in
    switch result {
    case .success(let graphQLResult):
      let validateResponse = graphQLResult.isValid(query: query)
      switch validateResponse {
      case .success:
        guard let data = graphQLResult.data as? T else {
          resultSubject.send(completion: .failure(CYGErrorType.default))
          return
        }

        let response = Response(value: data, response: nil, error: nil)
        resultSubject.send(response)
        print("API>> response")


      case .failure(let error):
        resultSubject.send(completion: .failure(error))
      }
    case .failure(let error):
      resultSubject.send(completion: .failure(error))
    }
  }

  return resultSubject.eraseToAnyPublisher()
}

Здесь код создает PassthroughSubject, и каждый раз, когда вызывается обработчик завершения, он выдает значение, переданное в обратный вызов через этот субъект. В нижней части функции мы конвертируем тему в AnyPublisher, поскольку тот факт, что это PassthroughSubject, является деталью реализации, которую людям, не входящим в эту функцию, знать не обязательно.