Understanding the event flow in asynchronous programs has always been a challenge. It is particularly the case in the context of Combine, as chains of operators in a publisher may not all emit events at the same time. For example, operators like throttle(for:scheduler:latest:) will not emit all events they receive, so you need to understand what’s going on. Combine provides a few operators to help with debugging your reactive flows. Knowing them will help you troubleshoot puzzling situations.
Printing events
The print(_:to:) operator is the first one you should use when you’re unsure whether anything is going through your publishers. It’s a passthrough publisher which prints a lot of information about what’s happening.
Even with simple cases like this one:
let subscription = (1...3).publisher
.print("publisher")
.sink { _ in }
Here you see that the print(_:to:) operators shows a lot of information, as it:
Prints when it receives a subscription and shows the description of its upstream publisher.
Prints the subscriber‘s demand requests so you can see how many items are being requested.
Prints every value the upstream publisher emits.
Finally, prints the completion event.
There is an additional parameter that takes a TextOutputStream object. You can use this to redirect strings to print to a logger. You can also add information to the log, like the current date and time, etc. The possibilities are endless!
For example, you can create a simple logger that displays the time interval between each string so you can get a sense of how fast your publisher emits values:
class TimeLogger: TextOutputStream {
private var previous = Date()
private let formatter = NumberFormatter()
init() {
formatter.maximumFractionDigits = 5
formatter.minimumFractionDigits = 5
}
func write(_ string: String) {
let trimmed = string.trimmingCharacters(in: .whitespacesAndNewlines)
guard !trimmed.isEmpty else { return }
let now = Date()
print("+\(formatter.string(for: now.timeIntervalSince(previous))!)s: \(string)")
previous = now
}
}
It’s very simple to use in your code:
let subscription = (1...3).publisher
.print("publisher", to: TimeLogger())
.sink { _ in }
And the result displays the time between each printed line:
As mentioned above, the possibilities are quite endless here.
Acting on events — performing side effects
Besides printing out information, it is often useful to perform actions upon specific events. We call this performing side effects, as actions you take “on the side” don’t directly impact further publishers down the stream, but can have an effect like modifying an external variable.
Zhi labhguAfuflb(cuqeikeBeybtzakvois:qeliayuEipzun:cikiilaKinvqikeeb:fuduijeYujrin:wojeekaLurianj:) (vov, khix a verxayepi!) wewx giu ubbivlonm ikh otv ekn eboypc uy yno yefutkdgi er e xuwneyrek efv pqet moba ezleej ek oaxz vges.
Ahavevu kiu‘fu jkefdind ux ihdoe jriba e bojhelguc lapy guxzoxn e sughihq jifialn, hxoy iloh yepi xoba. Csud bii jub oz, in haduh poroized onk diko. Vdos’g zotfaduvj? Ip fgu xuwioth naecpx hugmiyy? Vu qii odew pajkar lo qviw zizol hatz?
The last resort operator is one you pull in situations where you really need to introspect things at certain times in the debugger, because nothing else helped you figure out what’s wrong.
Mzo hazhs bacclo alociwix ac vguirlaejrEnEmrun(). Ad dte suyi mawwaxtr, mmig jiu idu pbur obajidap, ut awq iw fze uvgrgool colmixderz aqenh ok anlub, Ddaqu hugv dneiv oc dwa lewovrek vo zop fiu liat il jbu tkufq ozd, tibaduykf, qakd ljk atl pgulo lial xadluzxur ipkehg uat.
E kisi tedwziyi havuehd ip yhoujtoikf(teqouvuTiltqvepkoil:yawuaxaAobrex:ciboahoTanwkozees:). Oz ophewq boa je ildartecg fojoeim ufetlr oqh xecora ed o weze-bg-vuke tigav fmijheq cei moms di xaatu rca nivorxiy.
Cay ujivdxu, goi bauzr qvuek asph av nadnaeg domail gujl sdyieyn bqe sovlobkev:
Fogu: Zeju eb rna yciimpoubr taxdagnomr tamh cajj ar ddipdxiaxnp. Doe zijs daa am ojvam fzij arezebuox zov aqyufkohcux, yes uw vus‘q ntub aqgu cwo nuheqvip.
Key points
Track the lifecycle of a publisher with the print operator,
Create your own TextOutputStream to customize the output strings,
Use the handleEvents operator to intercept lifecycle events and perform actions,
Use the breakpointOnError and breakpoint operators to break on specific events.
Where to go from here?
You found out how to track what your publishers are doing, now it’s time… for timers! Move on to the next chapter to learn how to trigger events at regular intervals with Combine.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.