Я столкнулся с проблемой в моей реализации кэширования в памяти, из-за которой 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 не захватывает второй обратный вызов? Как я могу гарантировать, что оба обратных вызова правильно захватываются и обрабатываются? Будем очень признательны за любую помощь или предложения по решению этой проблемы!
сказав это, я считаю, что ваша проблема может заключаться в том, что Future предназначен для выдачи одного значения и остановки. Вы видите первое значение, которое завершает Future, а второе значение просто проглатывается.
Конечно, я отредактирую вопрос
Да, будущее — это проблема, можно ли добиться этого с помощью объединения?
@ScottThompson: отредактировал вопрос, пожалуйста, проверьте сейчас и предложите способ добиться этого с помощью объединения
Future — это однократный издатель, который выдает один результат (успех или неудачу), а затем завершается. Чтобы обрабатывать несколько обратных вызовов или если вы ожидаете более одного значения или события с течением времени, вы можете вместо этого использовать PassthroughSubject или CurrentValueSubject.
PassthroughSubject Также не работает, если мы завершили сеанс.
Я проверю реализацию, связанную с CurrentValueSubject. Если у вас есть примеры, поделитесь ими.





Ваш код тесно связан с библиотеками (клиент 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), если я это сделаю, он не будет ждать второго обратного вызова. что произойдет, если я не позвоню этому? действительно ли это создаст утечку памяти?
PassthroughSubject будет захвачен подписчиком, который его слушает (обычно это вызов sink), и его следует отбросить, когда подписчик отменяет подписку, поэтому утечка должна произойти только в том случае, если вы никогда не отмените подписку.
Спасибо за объяснение.
Я рекомендую вам сузить ваш код до минимального воспроизводимого примера - исключить ненужную проверку ошибок, добавить замещающие типы там, где у вас есть библиотечные типы, и тому подобное. Затем предоставьте образцы входных данных, которые воспроизводят выходные данные, которые вы считаете ошибочными.