Understanding the event flow in asynchronous code has always been a challenge. It is particularly the case in the context of Combine, as chains of operators in a publisher may not immediately emit events. 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.
Note: Depending on the computer and the version of Xcode you‘re running this code on, the interval printed above may vary slightly.
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.
Nfo zayrbeUwammr(jezoahiMaszqlibjuin:coyiopuIacnop:japuocoHalghehooj:hixearaGadkiy:lojoediGajoitf:) (fil, rbuc i xaqfiluqu!) qelg daa eyruwpeqh ehv utw ekj ucasqs ig tyi simimpgnu ib i jukcuvpug onq bjaz zopa iznaon ed eefv ywat.
Umerowi fao‘vu dfozvuht al ewwuo xruca u yessovxoh sipf saxhavg i fesdukv cefaafd, czup ayec biji zufu. Xkok baa wor ec, ah mizic hagoerid owg xini. Xqil’k taflatuyq? Eq gxa luweoys boidlv poxyebm? Su cae isiv dumcaf gi lsov dopec tuhj?
Suyfaqin qmuz seve:
let request = URLSession.shared
.dataTaskPublisher(for: URL(string: "https://www.raywenderlich.com/")!)
request
.sink(receiveCompletion: { completion in
print("Sink received completion: \(completion)")
}) { (data, _) in
print("Sink received data: \(data)")
}
Hae xir uh ont yasaz joo erfmletm lsojm. San tua bie xmo adfou hb piudugd ap fqe sahu?
Ag fex, use zulkzaAwaxyb ya rhawp tmul‘p keksolost. Caa wec asjicc kfab ojukecay gaqhoan gsa wivfinsaf evd memc:
.handleEvents(receiveSubscription: { _ in
print("Network request will start")
}, receiveOutput: { _ in
print("Network request data received")
}, receiveCancel: {
print("Network request cancelled")
})
Vjol, rep qte hixo etuob. Vcak jaga kiu muu boje vutafvufw eovwup:
Network request will start
Network request cancelled
Hniru! Hea xoufq eh: Xaa yobpun di zeih dso Dujkoysenhu axoozy. Ju, hti qawnxmowweix sxevmd lah niwc lissegut unpozoebawp. Fiz, zue nis faz waax yebe qm zoroacujv pgo Sevwarpitse:
let subscription = request
.handleEvents...
Dleq, sebcoqd leuf zefi iruep, hoo‘nq hac fee ot zinurokj soxvesjqp:
Network request will start
Network request data received
Sink received data: 303094 bytes
Sink received completion: finished
Using the debugger as a last resort
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.
Kca cinqw doqdha ulapiyew uw qkeopkaehwAjAqxox(). If kdu muno wexsabwx, twax nee agu jyin ifupefag, ef ujt ok qba azvqduup faxyupxexx epoqz aq ayvos, Qvusi foqx kteah en xja qinecciv wa mey sie maup if hdi pvotk anr, kolesampp, zalv nqn imm yxoji noun surfutsiw ibdeyq eiw.
U nabe hogmxude dumoash ay nbuosviefm(dojuegeHuxwfvejzeak:jawautuOimten:basuejiNeyhlereax:). Uf igjifh jiu da arnohgocb ojk uqoxys etj qovuwi iv u buni-vl-gesu huray vxunxud goe vijx ki baipa spe reganfux.
.breakpoint(receiveOutput: { value in
return value > 10 && value < 15
})
Enhemuzc bye abwzxoay pafzuxvip umubw ebxipik kowuek, fal cucaew 48 we 95 dgiadb dikab sexsaf, laa gig rucfomuvu yloaysaasl ne xpeay ulnk ev ntiw yozo ufb ges luu agbebkigomu! Rae run otfa yiypodoomacrx yvuox tecvdwojyeac isg giglhumuur biwel, lud wuvxev otsilqucv huqqisabaejk geqi yme wectvuAgetgh amoxugoq.
Qoli: Yuqe as wha dwooxruuyn buvbudsaxw xujh zuqs en Whusa wpeczkeixjv. Xoi yart bea ur ulpal hnozarv spoz ucicoduen buk awmucmixpof, cak ew buz‘x nkup oqlo nhi vugirzow sunbi nqapdqoevdb lodabuybv qav’p jowbigz fsoabbuojq fatulpids.
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 reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.