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 не захватывает второй обратный вызов? Как я могу гарантировать, что оба обратных вызова правильно захватываются и обрабатываются? Будем очень признательны за любую помощь или предложения по решению этой проблемы!

Я рекомендую вам сузить ваш код до минимального воспроизводимого примера - исключить ненужную проверку ошибок, добавить замещающие типы там, где у вас есть библиотечные типы, и тому подобное. Затем предоставьте образцы входных данных, которые воспроизводят выходные данные, которые вы считаете ошибочными.

Scott Thompson 27.08.2024 15:10

сказав это, я считаю, что ваша проблема может заключаться в том, что Future предназначен для выдачи одного значения и остановки. Вы видите первое значение, которое завершает Future, а второе значение просто проглатывается.

Scott Thompson 27.08.2024 15:15

Конечно, я отредактирую вопрос

Vignesh 27.08.2024 18:48

Да, будущее — это проблема, можно ли добиться этого с помощью объединения?

Vignesh 27.08.2024 18:49

@ScottThompson: отредактировал вопрос, пожалуйста, проверьте сейчас и предложите способ добиться этого с помощью объединения

Vignesh 27.08.2024 18:58

Future — это однократный издатель, который выдает один результат (успех или неудачу), а затем завершается. Чтобы обрабатывать несколько обратных вызовов или если вы ожидаете более одного значения или события с течением времени, вы можете вместо этого использовать PassthroughSubject или CurrentValueSubject.

Jayant Badlani 27.08.2024 20:11

PassthroughSubject Также не работает, если мы завершили сеанс.

Vignesh 27.08.2024 20:46

Я проверю реализацию, связанную с CurrentValueSubject. Если у вас есть примеры, поделитесь ими.

Vignesh 27.08.2024 20:47
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
8
51
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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, является деталью реализации, которую людям, не входящим в эту функцию, знать не обязательно.

Большое спасибо за ваш ответ. Я считаю, что PassthroughSubjectдолжно быть завершено, как только API-банк закончится, то есть подписчики должны вызвать завершение вот так resultSubject.send(completion: .finished), если я это сделаю, он не будет ждать второго обратного вызова. что произойдет, если я не позвоню этому? действительно ли это создаст утечку памяти?

Vignesh 29.08.2024 08:57

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

Scott Thompson 29.08.2024 15:14

Спасибо за объяснение.

Vignesh 30.08.2024 08:05

Другие вопросы по теме