UI updates off the main thread — is the fix receive(on:) or subscribe(on:)? Explain what each one moves.
This publisher decodes on a background queue and updates a label, but the UI sometimes changes off the main thread and trips the main-thread checker.
URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
.decode(type: Model.self, decoder: JSONDecoder())
.subscribe(on: DispatchQueue.global())
.sink(receiveCompletion: { _ in }) { model in
self.label.text = model.title // runs off the main thread
}
.store(in: &cancellables)
Decide whether receive(on:) or subscribe(on:) fixes it, place it correctly, and say what each operator moves.
Use receive(on: DispatchQueue.main) before sink. receive(on:) moves values, completion, and the subscriber downstream onto that scheduler. subscribe(on:) only sets where subscription and upstream work start, so it can't fix UI delivery.
- ✗Thinking
subscribe(on:)controls where values are delivered - ✗Placing
receive(on:)upstream instead of just beforesink - ✗Assuming
sinkruns its value closure on the main thread by default
- →What happens if you call
receive(on:)twice at different points? - →Why does
subscribe(on:)not affect the subscriber's thread?
subscribe(on:) only controls where the subscription and upstream work start; it does not affect which thread the subscriber runs on. Insert receive(on: DispatchQueue.main) just before sink:
URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
.decode(type: Model.self, decoder: JSONDecoder())
.subscribe(on: DispatchQueue.global()) // decode off-main (optional)
.receive(on: DispatchQueue.main) // deliver on main
.sink(receiveCompletion: { _ in }) { model in
self.label.text = model.title
}
.store(in: &cancellables)
receive(on:) moves everything downstream of it (values, completion, the sink) onto its scheduler; subscribe(on:) moves only the subscription and upstream start, so it cannot fix a main-thread UI update.