Reputation: 645
I'm getting my way through Combine bit I don't seem to wrap my head around some trivial operations. I'm building a Combine based to do some HealthKit operations. Mainly I have two operations:
func workouts(_ limit: Int) -> AnyPublisher<[HKWorkout], Error>
func workoutDetails(_ workout: HKWorkout) -> AnyPublisher<WorkoutDetails, Error>
First off I will use the first one to query some workouts, that's easy.
HKHealthStore().workouts(HKObjectQueryNoLimit)
.sink(receiveCompletion: { subject in
switch subject {
case .finished:
break
case .failure(let error):
dump(error)
}
}, receiveValue: { workouts in
dump(workouts)
// What now?
}).store(in: &bag)
Now I'd like to query the second function. It takes a workout as an argument and returns more information about the HKWorkout
. How can I query the second function with the returned [HKWorkout]
but don't know how to approach this.
[HKWorkout]
and call workoutDetails(_ workout: HKWorkout)
for each one so I get as a result [WorkoutDetails]
?What I have attempted so far it's not working.
HKHealthStore().workouts(HKObjectQueryNoLimit)
.map({ workouts -> AnyPublisher<[WorkoutDetails], Never> in
return workouts.publisher.flatMap({ workout in
return Just(workout as! [WorkoutDetails]).eraseToAnyPublisher()
.eraseToAnyPublisher()
})
.eraseToAnyPublisher()
}).sink(receiveCompletion: { subs in
switch subs {
}
}, receiveValue: { val in
/// Cannot convert value of type 'AnyPublisher<[WorkoutDetails], Never>' to expected argument type 'Void'
/// I am not able to get [WorkoutDetails] here as I'd like
dump(val)
})
.store(in: &bag)
Edit: Solution I've finally used. There are two options that I've finally managed to make it work and understand it.
One is @New Dev's answer to finally receive in the sink
the combined array and another one is to receive on the sink
individual elements to save on an array.
HKHealthStore().workouts(HKObjectQueryNoLimit)
.flatMap({
$0.publisher.eraseToAnyPublisher()
}).flatMap({
$0.workoutWithDetails
}).sink(receiveCompletion: { comp in
switch comp {
case .finished:
break
case .failure(let er):
dump(er)
}
}, receiveValue: { details in
/// Add the individual `WorkoutDetails` elements to an array
results.append(details)
}).store(in: &SGHealthManager.bag)
Upvotes: 2
Views: 4764
Reputation: 49590
The general approach is to flatMap
the array into a Sequence
publisher of individual values, then do any async operations on each value (another flatMap
), then collect
the results:
HKHealthStore()
.workouts(HKObjectQueryNoLimit)
.flatMap { workouts in workouts.publisher }
.flatMap { workout in
workoutDetails(workout)
}
.collect()
.sink(
receiveCompletion: { ... },
receiveValue: { arrayOfWorkoutDetails in
// arrayOfWorkoutDetails is of type [WorkoutDetails]
})
.store(in: &bag)
Upvotes: 2