Reputation: 95
I wonder if it is possible to access values in a combine-operator-chain that are used further up in the chain.
For example, if you have an array of strings, and you download a resource with each string, is it possible to access the string itself?
Maybe a pseudo-code example will help understand better:
let strings = ["a", "b", "c"]
strings.publisher
.compactMap{ str in
URL(string: "https://someresource/\(str)")
}
.flatMap { url in
URLSession.shared.dataTaskPublisher(for: url)
}
.map { $0.data}
.map { data in
// here I would need the "str" string from above
}
Help is much appreciated
Thx, Gregor
Upvotes: 2
Views: 741
Reputation: 534950
The answer that Jake wrote and deleted is correct. I don't know why he deleted it; maybe he felt he couldn't support it with example code. But the answer is exactly right. If you need the initial str
value later in the pipeline, it is up to you to keep passing it down through every step. You typically do that by passing a tuple of values at each stage, so that the string makes it far enough down the chain to be retrieved. This is a very common strategy in Combine programming.
For a simple example, take a look at the Combine code in the central section of this article:
https://www.biteinteractive.com/swift-5-5-asynchronous-looping-with-async-await/
As I say in the article:
You’ll observe that, as opposed to GCD where local variables just magically “fall through” to nested completion handlers at a lower level of scope, every step in a Combine pipeline must explicitly pass down all information that may be needed by a later step. This can result in some rather ungainly values working their way down the pipeline, often in the form of a tuple, as I’ve illustrated here.
But I don’t regard that as a problem. On the contrary, being explicit about what’s passing down the pipeline seems to me to be a gain in clarity.
To illustrate further, here's a rewrite of your pseudo-code; this is real code, you can run it and try it out:
class ViewController: UIViewController {
var storage = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
let strings = ["a", "b", "c"]
let pipeline = strings.publisher
.map { str -> (String, URL) in
let url = URL(string: "https://www.apeth.com/pep/manny.jpg")!
return (str, url)
}
.flatMap { (str, url) -> AnyPublisher<(String, (data: Data, response: URLResponse)), URLError> in
let sess = URLSession.shared.dataTaskPublisher(for: url)
.eraseToAnyPublisher()
let just = Just(str).setFailureType(to: URLError.self)
.eraseToAnyPublisher()
let zip = just.zip(sess).eraseToAnyPublisher()
return zip
}
.map { (str, result) -> (String, Data) in
(str, result.data)
}
.sink { comp in print(comp) } receiveValue: { (str, data) in print (str, data) }
pipeline.store(in: &storage)
}
}
That's not the only way to express the pipeline, but it does work, and it should give you a starting point.
Upvotes: 6