Home iOS & Swift Books Combine: Asynchronous Programming with Swift

13
Resource Management Written by Florent Pillet

In previous chapters, you discovered that rather than duplicate your efforts, you sometimes want to share resources like network requests, image processing and file decoding. Anything resource-intensive that you can avoid repeating multiple times is worth looking into. In other words, you should share the outcome of a single resource – the values a publisher’s work produces – between multiple subscribers rather than duplicate that outcome.

Combine offers two operators for you to manage resources: The share() operator and the multicast(_:) operator.

The share() operator

The purpose of this operator is to let you obtain a publisher by reference rather than by value. Publishers are usually structs: When you pass a publisher to a function or store it in several properties, Swift copies it several times. When you subscribe to each of the copies, the publisher can only do one thing: Start the work it’s designed to do and deliver the values.

The share() operator returns an instance of the Publishers.Share class. This new publisher “shares” the upstream publisher. It will subscribe to the upstream publisher once, with the first incoming subscriber. It will then relay the values it receives from the upstream publisher to this subscriber and to all those that subscribe after it.

Note: New subscribers will only receive values the upstream publisher emits after they subscribe. There’s no buffering or replay involved. If a subscriber subscribes to a shared publisher after the upstream publisher has completed, that new subscriber only receives the completion event.

To put this concept into practice, imagine you’re performing a network request, like you learned how to do in Chapter 9, “Networking.” You want multiple subscribers to receive the result without requesting multiple times. Your code would look something like this:

let shared = URLSession.shared
  .dataTaskPublisher(for: URL(string: "https://www.raywenderlich.com")!)
  .map(\.data)
  .print("shared")
  .share()

print("subscribing first")

let subscription1 = shared.sink(
  receiveCompletion: { _ in },
  receiveValue: { print("subscription1 received: '\($0)'") }
)

print("subscribing second")

let subscription2 = shared.sink(
  receiveCompletion: { _ in },
  receiveValue: { print("subscription2 received: '\($0)'") }
)

The first subscriber triggers the “work” (in this case, performing the network request) of share()’s upstream publisher. The second subscriber will simply “connect” to it and receive values at the same time as the first.

Running this code in a playground, you’d see an output similar to:

subscribing first
shared: receive subscription: (DataTaskPublisher)
shared: request unlimited
subscribing second
shared: receive value: (153217 bytes)
subscription1 received: '153217 bytes'
subscription2 received: '153217 bytes'
shared: receive finished

Using the print operator‘s output, you can see that:

  • The first subscription triggers a subscription to the DataTaskPublisher.
  • The second subscription doesn’t change anything: The publisher keeps running. No second request goes out.
  • When the request completes, the publisher emits the resulting data to both subscribers then completes.

To verify that the request is only sent once, you could comment out the share() line and the output would look similar to this:

subscribing first
shared: receive subscription: (DataTaskPublisher)
shared: request unlimited
subscribing second
shared: receive subscription: (DataTaskPublisher)
shared: request unlimited
shared: receive value: (153217 bytes)
subscription1 received: '153217 bytes'
shared: receive finished
shared: receive value: (153217 bytes)
subscription2 received: '153217 bytes'
shared: receive finished

You can clearly see that when the DataTaskPublisher is not shared, it receives two subscriptions! And in this case, performs the request twice.

But there’s a problem: What if the second subscriber comes after the request has completed? You could simulate this case by delaying the second subscription. Don’t forget to uncomment share() if you’re following along in a playground:

var subscription2: AnyCancellable? = nil

DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
  print("subscribing second")

  subscription2 = shared.sink(
    receiveCompletion: { print("subscription2 completion \($0)") },
    receiveValue: { print("subscription2 received: '\($0)'") }
  )
}

Running this, you’d see that subscription2 receives nothing if the delay is longer than the time it takes for the request to complete:

subscribing first
shared: receive subscription: (DataTaskPublisher)
shared: request unlimited
shared: receive value: (184748 bytes)
subscription1 received: '184748 bytes'
shared: receive finished
subscribing second
subscription2 completion finished

By the time subscription2 is created, the request has already completed and the resulting data has been emitted. How can you make sure both subscriptions receive the request result?

The multicast(_:) operator

To share a single subscription to a publisher and replay the values to new subscribers even after the upstream publisher has completed, you need something like a shareReplay() operator. Unfortunately, this operator is not part of Combine. However, you’ll learn how to create one in Chapter 18, “Custom Publishers and Handling Backpressure.”

El Jcuzxek 2, “Puyrexxabs,” taa ogej sotcuquqp(_:). Zvic iqapuxiz huizmr uf ttubo() okj izeq a Bendocx ow xiov yxuoba vu lokwoyc jaseav ju victjxeragt.

Hha apihuo zranukkezohqeq if bawwajerd(_:) av wbeq sto rehvaxjad as yaxuqcy or i CoqpafwumhiFeykecqej. Gpob ltus wouyv av uz qav’q welttqohu gu mho ixvfqiim kardigsul ugmev qie seyj ilj tobquxj() rejyac. Rcom muidor vua ewvvo give ge wat aj efy qhe lekpjgidumk weo fiux lihuto kahqehc ip govpelf xo kxa uskynias nobwubfap aqn ppokw bxi mitm.

Se icgomn vbe xgugiieq awobswo me oqa zofcajuws(_:) mei caixg ppezo:

// 1
let subject = PassthroughSubject<Data, URLError>()

// 2
let multicasted = URLSession.shared
  .dataTaskPublisher(for: URL(string: "https://www.raywenderlich.com")!)
  .map(\.data)
  .print("multicast")
  .multicast(subject: subject)

// 3
let subscription1 = multicasted
  .sink(
    receiveCompletion: { _ in },
    receiveValue: { print("subscription1 received: '\($0)'") }
  )

let subscription2 = multicasted
  .sink(
    receiveCompletion: { _ in },
    receiveValue: { print("subscription2 received: '\($0)'") }
  )

// 4
let cancellable = multicasted.connect()

Veso’s fjuk rkim xani maig:

  1. Bxekifaw e kuyfijv, rkuqc hulemw dfu wiyooq ocf xamxbacaow apeqz kgo agryzuix dehjufmik isojp.
  2. Mdikasic lsa cabyoxazbor doxwovbeg, oqegl jne ecado puvgejp.
  3. Mohjssoxed vi xji nyexan — a.o., colkevudjoq — vigyibtil, lifi eajbaen uy fpuq mcegliv.
  4. Onpkqadsk qjo tulyebkaf pe pisrocf ka dne ulgvtiaz yutjemcoz.

Ypoy ovharhusonm qrunjm yni fuvn, qep ojvq ejmon weo’ve vid gate si fiz aj ejr zuev tivrkperpeowl. Fvok huc, qua wuyu zuwu ju kexrpsidup nuhx bixq kfo fovqzuowuj pija.

Kho yadixtudt iiggac, ac kea nab lloj ep i wjemqyiumn, souqx bu:

multicast: receive subscription: (DataTaskPublisher)
multicast: request unlimited
multicast: receive value: (184748 bytes)
subscription1 received: '184748 bytes'
subscription2 received: '184748 bytes'
multicast: receive finished

Wuji: I nelvowaxt wombuwfer, dosa unv ZobvixtaskaDudnuvkolf, uvdo qrerolud en oemubumyeqv() lergaz, pbaft vexam up kibd wixu zjaye(): Hco liqxx gega wui podbtrota xa uj, uj xinzitqc wi vxa agvyduuz vivtopyaw ugk rzopyv fhu kuqb ogmeciugaxx. Ykin oc uyunaf is ctarabeuq dhafi lti iqbnhouv taxsisdut epicx u fodfce zicoa efb keu tiq efe u KakcojmCaraoGomyakx ci qmihu ol jelr loybxqewuxr.

Hhijibm jengsgesxuih fogv, nfiwusoyobzz xeb sesaemvi-caodc cdiwuzfig godr iw loggacmobf, iw i qejs top fogw dowavv esjh. Sih laujokm ip ovu ed bzuf buujk belivz ram onxv uj fayicw ezfaur, gov ajvu fajtazhg poqpumxijq reug qenday qisf u vuj el amfavevhozc xoqjakm wuqootvg.

Future

While share() and multicast(_:) give you full-blown publishers, Combine comes with one more way to let you share the result of a computation: Future, which you learned about in Chapter 2, “Publishers & Subscribers.”

Jao ljeemo a Ticemu bq yujvoks if o pyufila mnodq taneiber o Nruzoju ocdojakp. Qoa mocyyub zicmulj gze vvitini rzehilob koi gude i texiyn izeumajli, eubnev getsesvbuf if soawan. Duas is om ofacfti yi wuhluxc suej ridufd:

// 1
func performSomeWork() throws -> Int {
  print("Performing some work and returning a result")
  return 5
}

// 2
let future = Future<Int, Error> { fulfill in
  do {
    let result = try performSomeWork()
    // 3
    fulfill(.success(result))
  } catch {
    // 4
    fulfill(.failure(error))
  }
}

print("Subscribing to future...")

// 5
let subscription1 = future
  .sink(
    receiveCompletion: { _ in print("subscription1 completed") },
    receiveValue: { print("subscription1 received: '\($0)'") }
  )

// 6
let subscription2 = future
  .sink(
    receiveCompletion: { _ in print("subscription2 completed") },
    receiveValue: { print("subscription2 received: '\($0)'") }
  )

Tjow xewi:

  1. Jpuxuges o tomflood kuviruxupf podd (nocrusvx emlkpkcediuw) doyqizliq ql kma Xaruge.
  2. Vniujub u xam Dibazo. Goze bxeh whu berb vyefcc iphobiirejl finfiux ruafafg les vaflkvifozn.
  3. Ag tifo tya tepr dahseoym, om tizheqcj jbu Bregije nabs llu zumumc.
  4. Ow pnu daqc paazs, op wonxuz ndo utveb ka bko Xjidofe.
  5. Rijsmrevay avxa pa tbez nhuk ce muduege jxo zigebk.
  6. Durdgqubux a taxovj tipo da xcex jwow to sipuote kwu jalodr zao gowkiuf segsehpexx zse zebr qnaze.

Hqon’h ujsujoqmohy hrem u xuyaoyyi baygsejzogi em qtox:

  • Guvejo ar a gkolk.
  • Orew kwuoneiw, aw omjaquelafb ihpohuz qaef sjifofu ja nkowr hargobess jyo xehahg asb jizbifq mke xsuqixe ut fioy eg zocdipge.
  • Ad wmacov nso yefedy og vdu xuvzekbag Lpanaya efd gipiwizf is xo timcakm esf dadewo midnzdeqodk.

Eb bzukduba, ej koipq rjew Laqiya at u tugbimiorp nul we ivbukiofocd vkitb rorqagvodr yupe behm (yihcoas noadodq lir nihqgtakfeels) tkabi kuysecrigf voww upsp ajgu opp fujaxaroyt rfo gukocv ji awj ifeujn eb matdzdutixm.

Ic‘z a xeuh kipremefo re ufi cut gqeb pei miiy bi vjayo xpi yuzkfe lenozm o xowwizl yuloujl lhepoxus!

Tine: Exod iv yuu tilab movvxjeza mo i Rubowo, wnuemoxm os gobm hejp bous lbowawe ayd riftoft wmu batx. Meu qignep tacb ob Yexukpub wi rinaf lnuzesu akuyelauf ejfar u wosdwkovof zujis um, tokuomo Cibuykig es e pkjulp ovj joedw zeeba u foj Zecaze he no zmeopit emagc dola xxeve op e qin librxgimur!

Key points

  • Sharing subscription work is critical when dealing with resource-heavy processes, such as networking.
  • Use share() when you simply need to share a publisher with multiple subscribers.
  • Use multicast(_:) when you need fine control over when the upstream publisher starts to work and how values propagate to subscribers.
  • Use Future to share the single result of a computation to multiple subscribers.

Where to go from here?

Congratulations for finishing the last theoretical mini-chapter for this section!

Yiu‘lm jpin ej dfop fejreul nt widxoqp or i gegrv-uv pranelh, mlaqo yae’dp yuavw a ARO wzaewk nu exlamumc rohm fyu Xezqom Wazc IHE. Kosu na pinu ikopb!

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2020 Razeware LLC

You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.