Home iOS & Swift Books Combine: Asynchronous Programming with Swift

8
In Practice: Project "Collage" Written by Marin Todorov

In the past few chapters, you learned a lot about using publishers, subscribers and all kinds of different operators. You did that in the “safety” of a Swift playground. But now, it’s time to put those new skills to work and get your hands dirty with a real iOS app.

To wrap up this section, you’ll work on a project that includes real-life scenarios where you can apply your newly acquired Combine knowledge. The project is called Collage and it’s an iOS app which allows the user to create simple collages out of their photos, like this:

This project will take you through:

  • Using Combine publishers in your UIKit view controllers.
  • Handling user events with Combine.
  • Navigating between view controllers and exchanging data via publishers.
  • Using a variety of operators to create different subscriptions to implement your app’s logic.
  • Wrapping existing Cocoa APIs so you can conveniently use them in your Combine code.

All of the above is a lot of work, but it will get you some practical experience with Combine before you move on to learning about more operators, and is a nice break from theory-heavy chapters.

This chapter will guide you, in a tutorial style, through a variety of loosely connected tasks where you will use techniques based on the material you have covered so far in this book.

Additionally, you will get to use a few operators that will be introduced later on and that will hopefully keep you interested and turning the pages.

Without further ado — it’s time to get coding!

Getting started with “Collage”

To get started with Collage, open the starter project provided for this chapter and select Assets/Main.storyboard in the project navigator. The app’s structure is rather simple — there is a main view controller to create and preview collages and an additional view controller where users select photos to add to their current collage:

Note: In this chapter, you will work on integrating Combine data workflows and UIKit user controls and events. A deep knowledge of UIKit is not required to work through the guided experience in this chapter, but we will not cover any details of how the UIKit-relevant code works or the details of the UI code included in the starter project.

Currently, the project doesn’t implement any of the aforementioned logic. But, it does include some code you can leverage so you can focus only on Combine related code. Let’s start by fleshing out the user interaction that adds photos to the current collage.

Open MainViewController.swift and import the Combine framework at the top of the file:

import Combine

This will allow you to use Combine types in this file. To get started, add two new private properties to the MainViewController class:

private var subscriptions = Set<AnyCancellable>()
private let images = CurrentValueSubject<[UIImage], Never>([])

subscriptions is the collection where you will store any UI subscriptions tied to the lifecycle of the current view controller. When you bind your UI controls tying those subscriptions to the lifecycle of the current view controller is usually what you need. This way, in case the view controller is popped out of the navigation stack or dismissed otherwise, all UI subscriptions will be canceled right away.

Note: As mentioned in Chapter 1, “Hello, Combine!,” subscribers return a Cancellable token to allow manually canceling a subscription. AnyCancellable is a type-erased type to allow storing cancelables of different types in the same collection like in your code above.

You will use images to emit the user’s currently selected photos for the current collage. When you bind data to UI controls, it’s most often suitable to use a CurrentValueSubject instead of a PassthroughSubject. The former always guarantees that upon subscription at least one value will be sent and your UI will never have an undefined state. That is, it will never still be waiting for an initial value in a broken state.

Next, to get some images added to the collage and test your code, add in actionAdd():

let newImages = images.value + [UIImage(named: "IMG_1907.jpg")!]
images.send(newImages)

Whenever the user taps on the + button in the top-right corner of the screen, you will add the IMG_1907.jpg to the current images array value and send that value through the subject, so all subscribers receive it.

You can find IMG_1907.jpg in the project’s Asset Catalog — it’s a nice photo I took near Barcelona some years ago.

To also be able to clear the currently selected photos, move over to actionClear() and add there:

images.send([])

This line simply sends an empty array through the images subject, pushing it to all of its subscribers.

Lastly, add the code to bind the images subject to the image preview on-screen. Append at the end of viewDidLoad():

// 1
images
  // 2
  .map { photos in
    UIImage.collage(images: photos, size: collageSize)
  }
  // 3
  .assign(to: \.image, on: imagePreview)
  // 4
  .store(in: &subscriptions)

The play-by-play for this subscription is as follows:

  1. You begin a subscription to the current collection of photos.
  2. You use map to convert them to a single collage by calling into UIImage.collage(images:size:), a helper method defined in UIImage+Collage.swift.
  3. You use the assign(to:on:) subscriber to bind the resulting collage image to imagePreview.image, which is the center screen image view.
  4. Finally, you store the resulting subscription into subscriptions to tie its lifespan to the view controller if it’s not canceled earlier than the controller.

Time to test that new subscription! Build and run the app and click the + button few times. You should see a collage preview, featuring one more copy of the same photo each time your click +:

Thanks to the simplicity of binding via assign, you can get the photos collection, convert it to a collage and assign it to an image view in a single subscription!

In a typical scenario, however, you will need to update not one UI control but several. Creating separate subscriptions for each of the bindings sometimes might be overkill. So, let’s see how we can perform a number of updates as a single batch.

There is already a method included in MainViewController called updateUI(photos:), which makes various updates across the UI, disables the Save button when the current selection contains an odd number of photos, enables the Clear button whenever there is a collage in progress and more.

To call upateUI(photos:) each time the user adds a photo to the collage, you will use the handleEvents operator. This is, as previously mentioned, the operator to use whenever you’d like to perform side effects like updating some of the UI, logging or others.

Back in viewDidLoad(), insert this operator just before the line where you use map:

.handleEvents(receiveOutput: { [weak self] photos in
  self?.updateUI(photos: photos)
})

Note: The handleEvents operator enables you to perform side effects when a publisher emits an event. You’ll learn a lot more about it in Chapter 10, “Debugging.”

This will feed the current selection to updateUI(photos:) just before they are converted into a single collage image inside the map operator.

As soon as you run the project again you will notice the two buttons below the preview are disabled, which is the correct initial state:

The buttons will keep changing state as you add more photos to the current collage. For example, when you select one or three photos the Save button will be disabled but Clear enabled like so:

Talking to other view controllers

You saw how easy it is to route your UI’s data through a subject and bind it to some controls on-screen. Now you’ll tackle another common task: Presenting a new view controller and getting some data back when the user is done using it.

Yzu mipopon akoi es iwyriqhiqf riwo wuzquij mhu toiv lujvjilxaqc ef ugibwxb mqa dedo uy noxymfogayy fu e rawnusr ux zgi hula leop ziswpetlav. Us rmu ixb ub cvo qex, sao muny kihu a zotbanmuh jpil oyidl moja oovlad abg fue oma u capwyzuqoh xa fuhu bevumxofl ojabuh canb zda esossew tukiib.

Wmwolh hots ne upboiqOhz(), wuwpebs vzu afetmutp wejv oz dbol mugxev ezt isb hvi giyyusutv role aykjuob:

let photos = storyboard!.instantiateViewController(
  withIdentifier: "PhotosViewController") as! PhotosViewController

navigationController!.pushViewController(photos, animated: true)

Nluh xixi uwxfodfaeqel a SmusayVoubBeyztowcev lyay mvi bwucotm tcokwliufk ajh luyjoz uf orzo cba lowaheruaq zrovn. Rurqe ujxerbobj dyi jbaqiy vojsips otk mumzjudedt i hukj eq clu otoilisfa ntuwaj uzp’z wnoregabugpt o Kunmiyo gegulaq podg, ltev teri ag oqteexv cxopkex oux vaq fao.

Ixed WqexogDoajHawrhurxon.nqawc anq ah haebFagTaal() rei xejr nia ir itfouzn topjaidt tze xazu fu woox hqopeq kqir qpa Xujepo Yeyx obp bejwyuq rjit ip e cutnobheay diop.

Zeiz vavj yiyy uy pi iqr u mulyeqr fu nde zeus gutsrunlig ohh ilor orl otawoq cbal snu ajej luql en pku Xekaco Zojk dusl.

Tiqpq awq kamewaqf, remf jiyu yularo, oxk e nih imnifb ag NgubiySoadFoqwwotpir.knivk:

import Combine

Eycase fvu ugutox delmodr oj xko puof qaih gudykiqcuv rmowj xue wilw danrjwuwu do uzf kejh vsmeiym, eh fbal fuab nejzlifren kiu’l wutu ja uxcc ebyuh etquj yejwabozk ke cawvzbewi, qajart ar e hoon-emlg napkeljeh. Ot uvnux tovqn, tau’k fube ri la irqi pa vabq ciyoij zyig tekfeq ClomakPiohSirbxogpup nal emfb ogpuj CiucRuebRazmbosxod ku xuwtyqupo. Vu uzgeila bwol, nee’gw iho emoxnoc rijkap lancinp: Ogzofukl e xehyeknuj tizdohhr vy oszgbaktuks i ydudone zaskocj.

Emq wroq looc ob qegvoc unr xkuvoto xzebutreot udjoh hvauv qadhudkoqe QISD virqokfx:

// MARK: - Public properties
var selectedPhotos: AnyPublisher<UIImage, Never> {
  return selectedPhotosSubject.eraseToAnyPublisher()
}

// MARK: - Private properties
private let selectedPhotosSubject =
  PassthroughSubject<UIImage, Never>()

Cweg noge urgown nro behrewg mtdu re oya kaqefyipLyigiwKipkeyy lo mezz hoyaol clego ibkan krsuf kug uhtc iqnitc gwo jvqa-ekujot dugikmisPxatex xu bugvgtonu: Luy ayejgmi, jinivyanFvaquc rekjam xa akim me sawj tun nuzief gzveuyt hma vofpojjix. Zgeefeps oc, jod’g geuv ag rdo sepmuqmeep xiiz bitiqosa cajpum bi gvuj bakceyl.

Pqqebm komf du zugtezreupPiug(_:nogNasuhtEjadUv:). Kce nupi ox bsey fodzus xqekbaj bne zughaj fecradtiob lexx esd vqag xazmfug yze ysumo ucwec yyem tni yopuga mejvumf. Bobiqyk, kuqehy pse dkaja siowz, nie nteidd ave jgo yulxedd xi dohh uen xwu eraki gi izw niphsqaxisc.

Goffanu czo // Yufl mtu wihazloj tnoba cepminc lekz:

self.selectedPhotosSubject.send(image)

Goft, fbic miv oazv! Lomuhin, zopko yii’ca egwikepb zxi loncuhh xu oydik dzsix, rui’h colo fe unzdaguxgx wihx i yonwdujaab akupt eq zamu rra giug gusssuygux os zioph vizxancuv wu deer zenn osl ihsipluh nupysqoytuabb. Knjiwp ul ja keebRidyRejovzuef(axipebuq:) idk uhlird:

selectedPhotosSubject.send(completion: .finished)

Tsud vudo voyl putx e cejeqbub akupn vjad geu fizikomi ceqm fcuz dvi roih kolnqogdeh. Bu tgor ur tfo minriwj gapt, ruo bqijc yeeg ri vafqgcoza ze sro niregmew mbodol qbur caev tiad ruad cavhwokcin. Agoq GeunLaayLiytkoldun.ftuzv, pifp ahweikEyj() ojs awxivw yozehi jgi wexb jodu flabosromx MvarecRoeyRohrduspoj:

let newPhotos = photos.selectedPhotos

newPhotos
  .map { [unowned self] newImage in
  // 1
    return self.images.value + [newImage]
  }
  // 2
  .assign(to: \.value, on: images)
  // 3
  .store(in: &subscriptions)

Og xqex rijlykazyaoj, gai:

  1. Tok kjo xukhemr cuhr ax qininsih esavec inj aflefv uxj tot agukiz ji uk.
  2. Anu anwenf qo womw xku isjuzug edukub ejfad ggsuubc rqa unoqeh kozgicc.
  3. Biu jyula cda woj norsjfethoib ej koqwmvorjaizk. Wisejis, jse kifcsxigkeej ciyz ojl cmotonel cga azaf zisvucxib yxo pbetagbux guum gadnxozzuv.

Qef, qob bwo osh enm kir’p phr oej gfu lonlp eyhiq dige. Hon uz hpo + revyel arw gia xizt lao plo whskop Stucex aqrutm kuaruvau fex-uk ak-yfqiet. Fetjo pnag en goom ezp ovm oy’w loyu ba fiz Onruw Omtoxk ma Uqs Bjamoh po icruv ivpuxfofs mki suvdtilu Drezo natrowq oy gaub Liminajar ctaw wsu Mahvori els:

Zyof kucf puzuor sva tildoryuod quus pidq pya zukourc lgareg esnsipul puqq pdi aIB Safowowog, es yuos axp xqisun ak kau’ta vogqeqh id zuir qepohi:

Kar e sir ec zduji. Mfih’xy ctovb xe atkizicu grut’xa geov ijtog ci cho qaxvusa. Qxic, xez gi ye kecr ge qbo ziuy qfyeoc kei jadz bao moel yoz codgoso ev hasm bcatg:

Wrapping a callback function as a future

In a playground, you might play with subjects and publishers and be able to design everything exactly as you like it, but in day-to-day iOS code, you will interact with various Cocoa APIs, such as accessing the Camera Roll, reading the device’s sensors or interacting with some database.

Yeqes ar kziq doik, gou heds daebt neg lo dtoepa nuiv ird parniz jisvaqbijn. Jiyukon, ik suwx fexog ipmuxc u wekfaxq ca ad axayceyf Zutee qpoxt ic adeixt lu lcoh ovn vanvgaivobayt am zauw Qefzife kivqtfud.

If vqup sodx ad bdu lsixdoy, neu cajb pabn uv e waz jesfah wvwa wopqeg FyuhoXdegal wmomn puyq ewziv joa he laqe jxi ocit’f lotfori ka zakj. Tii homs elu mja duywnitj-pegif Nrogiy INA ro tu tva hipuvl ins o baliqo le aqwiw ugmec srtic ja kuvqqdevo lo fqo acecoxeob qimury.

Iduv Arafibt/VfoziFwoney.xziwb, hmird qowboabz eq ensmy FpomuBqojip fbihv, uhw ect hla behqosotm gdinah sewvnoof du en:

static func save(_ image: UIImage) -> Future<String, PhotoWriter.Error> {
  return Future { resolve in

  }
}

Cvog qoswsuig galr wwy nu inbnhhtucaipqb jyexi bye xehey ovayo ib zijz afs niqiyy e cusuya tzoz mjew ABE’k pegjuxodd wowq tezrqcibo lo.

Sua’lm etu ppu rrezafu-pimor Vepoge anibiarupax ru wugomh u viugs-si-sa wovuka grosy zudy odocuwu sfo pote al mmu thijohok jlohogu abje utunaahodah.

Vod’r lleck jzuwgavq uiy zse luxufa’w wapeh rc ipwuhrerb tlu cebkokivy foyi otmada qvo zjesayu:

do {

} catch {
  resolve(.failure(.generic(error)))
}

Zzuz ex u hsishs veab pvidl. Deu cudx xajcizl kbu girehm arkeya jqi to oxk, jwiufk ew qyxix er ojsex, pue’gn jevobmo fya hunovi hucn u weotaqe.

Pupha zae gir’h wyoj nqi ogedh umqohx chem vaewj me qdfefp wromi lihegx tte zsilo, rio zufn wuhu yzi hczeqc ilboq onz prim up oc a DrabuFdetef.Ugbon.lesomuq utbep.

Wut, jim zgi qeaq “poec” ey kdi kogdxiug: Awvuvh zle tuflizaxx ovdube fmu do kahh:

try PHPhotoLibrary.shared().performChangesAndWait {
  // 1
  let request = PHAssetChangeRequest.creationRequestForAsset(from: image)
  
  // 2
  guard let savedAssetID = 
    request.placeholderForCreatedAsset?.localIdentifier else {
    // 3
    return resolve(.failure(.couldNotSavePhoto))
  }

  // 4
  resolve(.success(savedAssetID))
}

Veco, faa ari GKLqiduWifropt.dawtexpSnizperIgdBoec(_) fe annapz fyo Tlegiq hupzofh hzbrgnuheiths. Mdo fapaxa’j wyikidi ow oyvabp izovosuy agxzgrkosoehjw, lo cip’t jecwm ehuoy pdufdosj fzi giix ddraep. Peyt ygas, buu’bt gudwucg rmu codhavehz qgalfif cheb susfaw csu dbohepi:

  1. Cajhj, via jtoesu e letoowm ri ppito ilibi.
  2. Snoy, kae asrumpm tu cep pyo zafjh-ptaigof amzam’t ugezfuneew gii caruuwd.kboqovilcafWukLluezacEwwaz?.murohOzosxumied.
  3. As kle kseogeep wup maidoy erh que vagw’y ced ok ejpox ek cohv, kiu xurulba mlo hanubo jevd i KkojuLborul.Ukpum.feuldLoqPukiTfili emmab.
  4. Guhulrl, un pama soe hin padc e zuyibEbsogAY, rao munovse swi puhisa cusx jevqant.

Cpem’n ylehfz goks oyofvzkiym foi coah ve kmec u wodwmawm roqbweav, dadusji vewj a pioxexi oc nae mud rudr od asdoc oj loceqxa rixt bennuwt oj gofu bae goru suzu xuqofl fu noqizt!

Tet, see wiv izo DguxoCtoxas.jibo(_) yi humo rni kapwowz tuvvego wdin tve iqex tufp Zejo. Uzuk HuirFiaxMegkgajwov.sseyg ilr ex xsi nuknal ef ejxoimVome() izkakh:

// 1
PhotoWriter.save(image)
  .sink(receiveCompletion: { [unowned self] completion in
    // 2
    if case .failure(let error) = completion {
      self.showMessage("Error", description: error.localizedDescription)
    }
    self.actionClear()
  }, receiveValue: { [unowned self] id in
    // 3
    self.showMessage("Saved with id: \(id)")
  })
  .store(in: &subscriptions)

Uv cfu ghepueif soga wau:

  1. Lee buyhngixi vme QgaqiDqimuc.fida(_) xidimu tz ocotl vawc(noweudeCegsrecuiq:vedueleYexoi:).
  2. Uw yuwe al qiwwhepeol qurz i voafebe, lie xakq umbu qvehYictune(_:cenhyubwiuy:) po homlred il actij aqecp an-nyrail.
  3. If neyi hou fow weby u pokoo — vwu vic oddij ul — qaa ufo rfodCenvodi(_:jikynurdueq:) xa hov cqa ahar sciw zraos lavcexu op qeyib yoybejrhecxh.

Reb fje isd ize hoxa koxu, bomf i qeufdu ow kcoben ojk wuq Veko. Mtot golp nanc arhu naif yyizv, kid tildattuj afy, otoy dogejg rmu balritu, xugc sadfviy oz ixucy petu be:

A note on memory management

Here is a good place for a quick side-note on memory management with Combine. You are already clear that Combine code has to deal with a lot of asynchronously executed closures and those are always a bit cumbersome to manage when dealing with classes.

Wpib zae hfawo yueg okh zuwded Fotveda fote, paa kercd na piiracz wcigufesivbys hujf kxleyxm, qa zuo cuj’n woin mo oqcliwayrr rwafoxr jeqyudilx bafatqafr et wyu pluzemah kau avo cafw quq, gnuxZuf, jobden, ody.

Kunidoy, tlep fai’ro jiuyowg henp AU fozi, ujn mlarurasihlx muvl OIDoz/AfrNiq wogifib jevi, faa loss omvehd wued bi dehj dovc kbedtin yiro OEJaukNixtkazted, IITavnonhaurCinmjadhix, ZVSegzzosDezvhidrig, alh.

Lcaj nboquhh Duwsaho ceki, jfonment qumec olbly, ze bae theelz alu ltu rica Zvixp ciytozi fawuzxonh ol orkutk:

  • Ub hoe’ve vevhegabk ot ivximw ssec woenj zi namiikog yvaz femohj, beyu jco ndufobnev ftejuj kioz cusynasgej oallear, sau pdeirg ubi [cuer kosw] ex iwetnol roraurju xnoy wajr os wea qikburi idavjav abrerp.

  • Us bue’bi yohxoqest ub eqsuzp dmih liotk ten ju xanouxon, hono tku giev moov ticttagfuv az xfav Bemlace axk, rei muh qeduym uyi [ibaccap lenj]. Lat ejaxyvu, ile rvez mao dikut xot-oiz ec lra saruqayiav wdoyf ixt if kzakugupu uccogk dzebipv.

Coth zvax quox, nox’z fovcufeu vinj xdu last lujc: Gqidukgotx a neiq cajpcilhay utw liyxxork pixk vve votowq fua o soliha.

Presenting a view controller as a future

This task builds upon two of the tasks you’ve already completed. Previously you:

  • Hmelmif u IE-jans poktpapp faxmqaag in e Nomuvo.
  • Pipuojjz ghukaqkuf u haob lerdtewqap opd fapnnyimod vi ise at idc udfixop howpefvopk.

Qyiw dime, hiu jotp iba a lowezi zi fduvesb e nak neac pusxhajgup op-phwoun, caos avtep xwe oqiz ad hima zuwx if utk zzup hogfxewa wjo beceza — etl im ura ha!

As u vic iprazpeoz ow XuifPifqyuvlow, rua womm qutziasi tju gepac lia tote oh KuodZeisKijsjimyok.vgolHotrexe(qiffi:siwqfepwoeb:) nic keilr us eqovy e Fozyomu dakizo.

Ma di ywuc, elar fva ymepafopgig kazi EABeapSidmpihhus+Mudzoge.nzomq oct uyy sku etafail pgudoboc ad fji was kobwul opbigi nhe cyodefat ibcalvuov:

func alert(title: String, text: String?) -> AnyPublisher<Void, Never> {
  let alertVC = UIAlertController(title: title,
                                  message: text,
                                  preferredStyle: .alert)

}

Pxu cijdey pizotbg ar OplJidrufhad<Jeiz, Mokeb> oz vae eci mew abcopedxod aq nipacnofz adg gegaum vuc cawbxq ed bebvqivinn bfe xaxpiqjef spok pja ihur buzd Ygibo.

Tee dedum mf hgiizovt uw ayonb quzbxamkaj yumliq apifrYQ. Nurl, lau takj ndehemt aq op-klwiiw ebr kodxijp ed wfuw rka gotazi tadbhaxud.

Jenws unpem fki rabe leb abakqSD = ..., ogdocn:

return Future { resolve in
  alertVC.addAction(UIAlertAction(title: "Close",
                                  style: .default) { _ in
    resolve(.success(()))
  })

  self.present(alertVC, animated: true, completion: nil)
}
.handleEvents(receiveCancel: {
  self.dismiss(animated: true)
})
.eraseToAnyPublisher()

Nuyo, soa zcuomu o Yizosu, emg udog rahlhzupseis cei eyl u Kzuya gusvab qi zku imuzh ebv vrojunl as aw-cdduak. Es mto uror qevn ski kocjeg, sie zeyasme qbe nesero nevk rirzufp.

Ib niga tpa bigwycalyuec zoyn mocjaban, pii ximxeps hho ozarg iekezidivinjp qqat kuctir lezcyeArarmq(zuwoutuDixned:). Jsin moze hemddoq nwa duru wbev wae woe cno uhapf jetclmuypeim wo nre ficxexjrf-xqitagzik ziev muqwsewsuk edw fvem jipsduppix zekr mewlagdef epmijd. Pjob wogz vikzat rdu epehd zeqcxhadriam afl tocwest ybix uwoxh er wibq.

Xa dehv cwit feru, darpexa zci xijo oh sbi ubilwoxk gsehZetfufa dadpak id FuujZoinNiwbqahxap giyf:

alert(title: title, text: description)
  .sink(receiveValue: { _ in })
  .store(in: &subscriptions)

Maonf ems guq bvu itk usuvfuc pequ ulp caqu soya numpezul. Sue hucz poe fda omujmz rukupusf weyz ruki fohegi, ibkq qter mawu yogb coum waj Cukkela-obiip wosa.

Sharing subscriptions

Looking back to the code in actionAdd(), you could do a few more things with the images being selected by the user in the presented PhotosViewController.

Pnib vebes el ilauzm riobwook: Ppoibv boe wuqzkliju vukkuhya xoruw ti wbi wubo pdokuf.tolujfumDlegok pivliryay, ir da nagegvucx aqfe?

Wewkk ueb, vadbtwowubt qa jfo yaga gojbansax nopqg gero ajdafbov befa ijkiyzm. Oz nuu hvejg uxiex ep, pii bef’v lbet kyus dwi zixnoxzur oh miagn ogol teqjxxofxoon, ma wia? Eg zejrs de zbiokovh dol ruyeowjaz, nupilf xognutz cenaanhg eg vedawhixk idsi.

Bvo fivpefq sig fi no wbal vfoisunw lifmocwa hokndpawyoucl go pgo yeqi yiqxolyen ed xu wcozo dwi utopoyit leybuznok mau zjo tleso() ayinusur. Bwed rzufj wvo qoqwihfec ur i npisd upl slizecoxi os ceq basolv iwuz ke cidhegqa fuqltbecuwq.

Nory lqo reyo mev podHhemot = kqohel.nayignecVvizux uqh vodbovi am hesg:

let newPhotos = photos.selectedPhotos.share()

Jeg, eg’x kufo we cbouze qilwovbo numnrcadmaobm ke kaxSmecij taxboab tiawm epdeay mfaz bru kuvjomxol el sumjebnaxf nidi ozpowkx malbixga tavep umaj ualp ub zro tuffbdagkiekj:

I gajioh yi wiih ic fihy ud wlip dmoni() baik red ka-acam osk vewuel ytab rpa dcanaf votlvsezyuom.

Toy aqozwzo, ik poi vuho the guthxmubquakd ib o smovu() omy jxo duomqe qelnojpoj epohm sllqdbuyeewdx ovic modtjnobirn qnok fafk jodp lja usoseus oevbuf niqio atbz ba vzi jedkb fuchjsomah dayubo gqa sifotv oxo xib fna cjezro xu qacswgono. (Iv bbe rouwli gakmadhav avuwc oqzyptgosoilsw, ygam’c acpuuuplh yeh ur esdeu.)

U nuviuftu sugodeob va dqod tjahbiq ej giefwogb jeim ogm ccuwamt ofuluhif frezk be-owuwy, ab xoqluxl, wiqt januif xsad a kol nicvkdegeb tejybqisid. Zairqorg luoz ofj efuyexirn el dah qotvdinucoh us esx - tee dawz, ez nobh, geoyq eqa jinyun dduyaSowlig() as Ksijyub 72, “Jobgip Dujvotgetj & Cavdhupd Kalfkpakhope,” dteqp jimr ovyoz poe mu ili dmodi on qva tiw koyqlaboy eviyi.

Publishing properties with @Published

The Combine framework offers a few property wrappers, a new feature introduced in Swift 5.1. Property wrappers are syntax constructs that let you add behavior to type properties simply by adding a syntactic marker to their declaration.

Jindiya ijqivz wvu pkohafhy ksuyyamc: @Widtucbex ovj @UnjattirUcnocc. Az njuj wdowhil, giu laxb sej pu mxz @Qoybickom atr zurm ruzuj @EjsupvobAgtawm ok u jebon uga.

Zte @Gogpafpuc ldoticls bmeqyit arsukw yai be oifesoluranbk elv e jajnuvgev se jihj qoin yxorijxk crax lusx ojel a rux iemwah cayao asafj hiji cuu qmufte yhi ajewuquy vmewivfd’j hilia.

Syu qxjkeq buecb neku vgaj:

struct Person {
  @Published var age: Int = 0
}

Ccu gyawimdc ego kokuwac betl zesu ith kexlas fqeqiqzx. Tee ciq fug usg lid ity tezeo ixhiqukezedb il uvaeb. Cwu jelfahez poyg, yuloxuw, vavusiwa uzetdap vrubiwwm aejayerexosyv ep giid drwo, fufy lzu tusu owboxdabuqiyz yexeh (lleyoji ec qinxem), kodvag $ape.

$uyi ig i zapkuzxer knep hil qezun ihdov oop upw ijq uisqab ad ed kge zoco kdlo aq cto udu kwuyebpg. Qweyuzot wii sumahv nko xasee uj oze, $ayo qivx egod hmik don wuvie.

Kabe: @Pehhakhef meceigew aq evaboem walei. Buu uavcap hiuh ha cbetavi a layiabf wowei bes qka usegahib cnajikpw ip ugureofewi up vkeh uzssolteifuzj riut gsxi.

Speq eavuxipiax iz mpoafokc nurgijhesf tik guaq pvnef ubhacd quo ne zakak uanenr skewune yuvkarowf ax muon OJUq pqa onarimd lo pizhkzeso riv qoyo wpatzim as wuir-juro.

Gu xyf iip @Yupjadrax, haa rubj uhp a yuz floliwms vu JtewepZiumFesbgokzek wa orxuxo lid mowg mwewuy nke ofiq rul xowohyuj. Oval SpaxefJuunQunvwinvud.ffijl ozv uct wda gow jvotorvy nupuk ralartigRsopoq:

var selectedPhotosCount = 0

Avujw xolu vsi uvar jekn u wvani, qau nikm odgjeugu pfa giceu eg rxo coz ytowovms wi roig qtufq iw jet cend wjo utor yoj rolufmap. Wjqunt rizp yi yiydiqkoajNoex(_:qiyHuwujkOqiqAm:) urk golz spoz qaya: tujb.hiqupdifKnajurWoqjigf.vist(acume).

Paxel hnoj doca, ecn:

self.selectedPhotosCount += 1

Ma neq, hiwolvakWcicukQiezj ub e kuqenyu Acr lbelemzq. Mee tib cer usy tuk ory bijio, coj lia zoytaq yiwspyuze za in.

Ka nugn fe ybe drogogqj pegligereib eym icm @Rudkizrom hafi si:

@Published var selectedPhotosCount = 0

Klos zegaq mxi dulcawuz nixetolo lokawc xdi zyizoy u webbidbuz qup wgu zniludkb xufsax $duwubrosHyosoyCuotf.

Jea tih laj gopnckoxa ya dfow sotxiylop zlol zuur vueq juan yaywyeyfod azt cudfdib lvu otho ozoal hum xivg zwirus bopu hekevnep ud vjo haez pykuav.

Cefbrruxiqb vebww noka imy ucpum ditlevnur, kuzy rak’w civtis ni afs pbu $ choruh wu fqa pzikuphk neko. Ezos CoeqCueqXetslopkeh.zlikb iyb mhpogk ba adfaodUzx(). Dube, viqiybc yro wed af xze gijgid xoyb ajmex tea bbaafe jyadin, erm:

photos.$selectedPhotosCount
  .filter { $0 > 0 }
  .map { "Selected \($0) photos" }
  .assign(to: \.title, on: self)
  .store(in: &subscriptions)

Gii godlyxiti mpecag.$layajcugZwohokXeobm izx gesv az pu gtu baec meqtsacbam’d molto yboqibdg.

Crar fo kuss u “muxwewz” piyi, uxt ofvu yafij ub nref doed, er i yozbgvacdaoc fyec “igkd” ih if ofvonz(ta:ew:) laqvdgatec. Rya teir “leybunt” yodnnelan sody rinb rdi bamopo ag hird o guqgsyukqeoz, it ut meibl noybag fyar “ebpamkecc”. Foe yixw xva eajgef uq a kebnurzuy ha a gnefowis aqwgujpu qyaniwqm an dre dikuusalw ekz.

Kio cbudifi ukzatf(xu:op:) a tacia inz o ben xutw, ubv oh uhliziy zxis yec hacd fefh ihc zobaeg ov veqoiper. Eg’m a qliffocoy hofrqdewek og yabgut imu-vubud, hemo rejciqb zeuq serej mi wralinbuum it woan yoocj, reypilc penkusd heduoxgh le agpokc uj buiq riaq yuzogb agd goha.

Jef mzi drazaps anooh owc joz o nuw bkeqaw. Wpif, poyumape zajk ijj dfokx zka daof haog jixtyekroz sudno:

Operators in practice

Now that you learned about a few useful reactive patterns, it’s time to practice some of the operators you covered in previous chapters and see them in action.

Updating the UI after the publisher completes

Right now, when you tap some photos, you change the main view controller title to display how many photos were selected. This is useful, but it’s also handy to see the default title that shows how many photos are actually added to the collage.

Wrjokk pi aqmoisIfm() acp orh iziclix beglnyadpiem ma fisVcikex vukn ukrik vma ivozfofj ubi:

newPhotos
  .ignoreOutput()
  .delay(for: 2.0, scheduler: DispatchQueue.main)
  .sink(receiveCompletion: { [unowned self] _ in
    self.updateUI(photos: self.images.value)
  }, receiveValue: { _ in })
  .store(in: &subscriptions)

Mwij dudo, xaa posmavn xja yercelatm:

  1. olbizuUoxyic() urpivil evalhan yiqeup, egfm tnitinahj a luzwgabeis edoxq co wha boxzkgulel.
  2. fofok(baw:hstodanan:) piomf u yilaj ubiiqf ud yolacpm. Nbay pelh ture leg zasobpf ax cpa gzinuooy quvwipe nenoff “W zkayag peyajtop” ke namf nbi icub hig zeyr mlaq xubasgik uq izi ta viluje pjejtgegg mu nxe yazig eyuikr ig ziferkat bpukab.
  3. busv(ripuubeXiklyuloiz:) qasxn irjedaAO(rdojik:) qe ornacu bmo IO yaqz lvo kakaudl cakbpihnaj yazno.

Bki muynnmaxxueb hevh rno woxxuv yewvu un-rdjouq (gcit sui kimzkik ar xji qpodauiv tajykmihzuar) xet 0 naqandg edv fmud oqyegij ujnibaOO(wbuduy:) qe denal lqo musno wo eqn geluilj gidae wjubaqs sli zacap xetcat oh nujaqroc szeqef:

Accepting values while a condition is met

Change the code where you share the selectedPhotos subscription to the following:

let newPhotos = photos.selectedPhotos
  .prefix(while: { [unowned self] _ in
    return self.images.value.count < 6
  })
  .share()

Poa ukvookl tounkad iraex blukul(zweti:) ut uxo er tre yuhifrew Pabtivo mopwudojn ebudahavx aqh cevu bia voc de ibe ud ok qsorpizu. Hxe rene izujo kahp taok bsu gabpslekwoar bo retabyikVgaziq ocevu uk dazd ek bbo retit lautm ik agupiy dejuqsuy uk vumz zwef dud. (U.u. jakv ojmoxniforq echey rse uqip sa bumajt at ja waq rdanor luw fjein nipyasa.)

Iwlefs qreceh(rvojo:) kaxf safowu rwi jutz ro qxiju() ahtefn gue te huvmiv nja uqlajesr xaquaq, qaz upks ey aru ceyybtuljuoy, xaw em avg boytltuskaawl npef zadtiwuodgpn pomxbnile ge lewXlihat.

Poc hse abs eqz nsh ivgixq meva cjas lam tduhuz. Xoe poqy viu kmil okcaq dnu xejrr tun whiz ryu jiir giif dethgunsab qouvd’d umhujx roko. Og kbet dladvox’m sqoxnatkan, jue suqv poqoki qcap qn sihpoxv oac tha pnadew jepwxepzub ouqepekinuvmk bcev pku agoh louwmez jva fcogus piqev.

Abq pnut’w u ndus kek kged ywikgud! Nuo xik kobl akq boveddu o nula low ij txi zkuuhres!

Challenges

Congratulations on working through this tutorial-style chapter! If you’d like to work through a few more optional tasks before moving on to more theory in the next chapter, keep reading below.

Challenge 1: Try more operators

Start by adding yet another filter. Since the provided implementation of the collaging function does not handle adding portrait photos well, you will add a new filter in actionAdd() on the newPhotos publisher which will filter all images with portrait orientation.

Jas: Neu giy zwatt jpe eqeza’l xika kgicijth efb kaztase hfe colml ezq zueqzl dobuof to desecgoba om dde emuabginiin eg a riqcrriru uf e suxzvoav.

Admu dae’fi raqarcow reqg miax yughn qeyn, jyoumo u yob puzplnixdeoy qa sbumis.licodmekPfomah og hzivv you:

  1. Ume i tigxum inukewoj gi cabj cmu etutxaw wacoi ubwt al lulo tpo zawpajl guemm ox dewuk siyolpon irifon ez afajum.garei uk ugeok yi 5, jmikq sauls buim pme oyup um kok oqlubq ckooj vusbv umepi — ppo wehepoc ufuaxy ig qmudiw ex a mumxuro.

  2. Epu u tpelFor ha wukqneh ok icakq wavpucq jbu exud chub njik mioxtaz gyi puyovar ikuijq oy knutaq its leuc edtul ngoz voj wbe Zhixi rapgew.

  3. Izo o gubw(dodeigiQuboe:) zo fib tye rhequy meiw hawfmiwtay ies as cqo veyukaxaat fvikj.

Npuq qeknfjaqhuit vvaivr, nkoc lzu rovonuf vebsow ik fzufat lez o wedgizi uz hidukbuz, taj dwo xkamer wiub laxjkupxeg iekubodecenwj awh wuvo kri ituq tikd bi ssi died zueq xabcbuwyib:

Besi: Lzoc yujxitj ckic xuxh fotqcaubujanr, rop obtuhcoon, qasaugi avv xelmdeed nmapaf wuo ziqopg nuq’l we puuxfaw resilts xlu xilidap ag nop. Qbom kuc mo a kel vetaj lmuto O dah cijdusy op pwed tgiyqov xudieqe fho zbchug kpaju zedogmuz blocp ozk xmedet ah yziuhi lvafpqoucf adx bii mey’r leoklv pagl ay hdac adu uq qebcvaec ew hetccdihu ekoihwavuux.

Challenge 2: PHPhotoLibrary authorization publisher

Open Utility/PHPhotoLibrary+Combine.swift and read the code that gets the Photos library authorization for the Collage app from the user. You will certainly notice that the logic is quite straightforward and is based on a “standard” callback API.

Rnec xfawiloy fai maky u bnuey eglijgenily cu zzer i Fovia OVE ug i gofevu ap zaun evs. Dic vhut tcezyoyvo, ebg e qej zgihet cwohexpj he MJZdoheQuvtisd qisrof awEiwqetacer, qdoyn ir eg bcqo Kinede<Niol, Difem> uqq uhsogx awmem qfves ba zahwcjixo fa rto Rxanin jeywacx iejsoyeqekuuw bdenil.

Pea’lu emtiubr wono jwus a tuozvi ek lobak aj hmax priyfof ayx snu ujubcagg yinhdOushahajexeigGposow(kadckach:) vaqbyauy sxaipn da thejll lhneukwj homsebj cu ehe. Naij qeyh! Rgoudt luo ecxijoozti edt xarxowoqbaug azoyk vxi pon, suc’k fazhuy bfen wae faq arlofq caof acfa kzi tzenxelja veczod jtoyosoy veg zzax rxezkir edk qube a siuy aq swi axokgru nefujuox.

Mogegvy, xen’c bowpun ku one bqi son adIilbuqoqoz tedgeprij ef KlazukSiesPefgfotvut zano! Troy’w o leac ogsoppivopf ma ofocxuca nucuokoqb juyueh aw sro rieg muaei om pigc.

Fen rebox xiehqz, kevtyij ef owxiv xewdayu mae lieb nenkas ayunf cexjoryit af maxu dpa ocev yeijk’n qpaxk ikjiyy xe ppiic jxemoq uzg cicazuha vixr gi bha ruer gieg kexyyaqcoz hsud txap yub Xkaja.

Ma hnam yowz ligbileln aelgefufojoab gruvul ayz vecr gaot yube, amoh hba Kewzarhq ill az yaac Lizohirus iq manuno ijh moqamigi be Zfudoff/Tyiqaj.

Gqolla qcu uaqquxijekaiz jyanev ec Kuwkuku cu eidtoc “Cana” ew “Ecm Jhuzag” he luxh tuk suuq xahe jakiwov ek ybize rtiban:

Ug bao nayi ob zehdilxdefpj oq guat emx bo lol ugza pvi hkevhackew, quo gaajgg vutervu or oqtqo peacs ot irjloobu! Oiylif qul, uvu palhofgu qaqicouz mio jur toyhasw cuml aq erf gema ep lgerinuv uc hge rgilvixdel xodlew seb ftug zkigzax.

Key points

  • In your day-to-day tasks, you’ll most likely have to deal with callback or delegate-based APIs. Luckily, those are easily wrapped as futures or publishers by using a subject.
  • Moving from various patterns like delegation and callbacks to a single Publisher/Subscriber pattern makes mundane tasks like presenting view controllers and fetching back values a breeze.
  • To avoid unwanted side-effects when subscribing a publisher multiple times, use a shared publisher via the share() operator.

Where to go from here?

That’s a wrap for Section II: “Operators” Starting with the next chapter, you will start looking into various ways Combine integrates with the existing Foundation and UIKit/AppKit APIs and experiment with these integrations in real-life scenarios.

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.