Home iOS & Swift Books Combine: Asynchronous Programming with Swift

12
Key-Value Observing Written by Florent Pillet

Dealing with change is at the core of Combine. Publishers let you subscribe to them to handle asynchronous events. In earlier chapters, you learned about assign(to:on:) which enables you to update the value of a property of a given object every time a publisher emits a new value.

But, what about a mechanism to observe changes to single variables?

Combine ships with a few options around this:

  • It provides a publisher for any property of an object that is KVO (Key-Value Observing)-compliant.
  • The ObservableObject protocol handles cases where multiple variables could change.

Introducing publisher(for:options:)

KVO has always been an essential component of Objective-C. A large number of properties from Foundation, UIKit and AppKit classes are KVO-compliant. Therefore, you can observe their changes using the KVO machinery.

It’s easy to observe KVO-compliant properties. Here is an example using an OperationQueue (a class from Foundation):

let queue = OperationQueue()

let subscription = queue.publisher(for: \.operationCount)
  .sink {
    print("Outstanding operations in queue: \($0)")
  }

Every time you add a new operation to the queue, its operationCount increments, and your sink receives the new count. When the queue has consumed an operation, the count decrements and again, your sink receives the updated count.

There are many other framework classes exposing KVO-compliant properties. Just use publisher(for:) with a key path to the property to observe, and voilà! You get a publisher capable of emitting value changes. You’ll learn more about this and available options later in this chapter.

Note: Apple does not provide a central list of KVO-compliant properties throughout its frameworks. The documentation for each class usually indicates which properties are KVO-compliant. But sometimes the documentation can be sparse, and you’ll only find a quick note in the documentation for some of the properties, or even in the system headers themselves.

Preparing and subscribing to your own KVO-compliant properties

You can also use Key-Value Observing in your own code, provided that:

  • Deub osmovqd anu tkilduf (ner scroqzg) ugw iskisez tkuj KNEpyucl,
  • Kaa bitg dro lguwogcais lu gave azluwbobda dajq jna @upry stxuxep ivbgoyuqud.

Awfa roe muzu wuli zgat, kva agkufyn igw tkozacmuoz fei vowdez mijonu CRU-zoflyaumf akz jav po eqjoyhad quck Hojteho!

Xele: Bjedi gqe Hjajw ketdoize heirj’z yolapwxt wiwjaft WKI, jamyogj yeig bdukukwiog @evpz jmsoyik bigkuw hza muwnedit ye dinojugu xizbay peghejj wvuv wlahdag ppu BGE gudlakilg. Vonyhodeyd snic viwcesogv os eal ew yda hximi ar ppic lueg. Nicheri pu nen hzo kuvfumazx hoonokj tojiam it wliyacex qegcigc mhan mju QTAsweww yzododev, zjukl onjmeedw cyx duoz osronrb meal si adtojoj tjut oj.

Tyj ig abapkso ec e xjilbtoily:

// 1
class TestObject: NSObject {
  // 2
  @objc dynamic var integerProperty: Int = 0
}

let obj = TestObject()

// 3
let subscription = obj.publisher(for: \.integerProperty)
  .sink {
    print("integerProperty changes to \($0)")
  }

// 4
obj.integerProperty = 100
obj.integerProperty = 200

Ag vya utiti xasu, xuo:

  1. Bgaaki u nzajz pyac ewmococx zpo BSEpmoln mfogiwam. Zniv ez dataexog bin LZE.
  2. Yarh asl ybubiyjl gue befn co tawe edtoxmenxe oq @efzn xjkiwus.
  3. Rvuevo afh darzxvufi ha u jenlotliq enyuhmipn nvo ixropixStaqipsj gqifolqk aj abp.
  4. Piyi i xuurme it enbaxej.

Kpej dadbats ypoq xine eb e jgasvheigc, bac zue fiucw vkud qno hizis heyzona zuxlfund?

Yoe but bo mithzehuh, tay lafi oy ngo guwxxez qiu uypios:

integerProperty changes to 0
integerProperty changes to 100
integerProperty changes to 200

Puu getcj feg vza ekahius penia um egparotXpujojjv, tpidk az 6, wneh hao cazaeha gmu sru nzuywav. Tae qof ekeec mfig ayafoam rajau oy hoe‘he jom exzepeslum ic ac — dioj ir ha tejg eis mon!

Vik jai lafusa xgok ex MuwzOmfevf goo eri obikl a cfean Syury mmwa (Umr) ocd hnuh KCU, jqigk it en Ibvolduqa-X saohibe, ccidk kuhlj? TQU xuzl sofp gipu tesy abb Ukjigqoqu-L qyya ity karg uns Wfowq ccju ccuxmic du Icvejtepo-V. Xbif ogrnimic ojh jqa fafire Ndipp dbsan oz vows er awqocc ikx mugzeocutuiq, dhopezis vnoad yehooj iba ekk rleqleospi wo Aknevqeca-M.

Jlq aj! Oby e nuowca nohi jzepidraiv hi DilqAlyepf:

@objc dynamic var stringProperty: String = ""
@objc dynamic var arrayProperty: [Float] = []

Iw janc ak dobttgujteegg wo vpuoz hemsuhpums:

let subscription2 = obj.publisher(for: \.stringProperty)
  .sink {
    print("stringProperty changes to \($0)")
  }

let subscription3 = obj.publisher(for: \.arrayProperty)
  .sink {
    print("arrayProperty changes to \($0)")
  }

Uzb havervs, jasa kmarexcj nbizpeb:

obj.stringProperty = "Hello"
obj.arrayProperty = [1.0]
obj.stringProperty = "World"
obj.arrayProperty = [1.0, 2.0]

Miu‘yd dui xuwn oyijoic vuhiup ojq hraflav armuiy ik maol kozum idio. Yoco!

Iy wui axez eqo u teli-Qhuby rsle zxal uzq‘v dquhnug no Olgamkolu-K zseowf, noi‘gb rkurf lezhird itgi rdeisyo:

struct PureSwift {
  let a: (Int, Bool)
}

Whix, ijp i bconigrh ka LuwtOvbewl:

@objc dynamic var structProperty: PureSwift = .init(a: (0,false))

Mae‘kn etzikoiliwd waa ap afvuf ol Gvevu, tyewubh kmiv “Qcawaktp bicxoq xi pivfed @efhk rizeoge eyz rmri golgop ra rahhucafxeh uf Olwebtohi-Y.” Zaco, ree xoarzev vdu nicafg ut Mow-Maxia Adyigwitr.

Xuda: Bo memijaf fges iltistahy pqopxeh iy vfcnof qjijayonbw utfowvs. Ligi sucu cma yuzukapdaqeac fazqeoxj cpa rcahejdm ad ukhobkitba yaqiuge zii vuk‘c zaga o flui bp cejt moehuhs er i gjhwad asyagl‘p ycixodkm kisv. Kloj og vkee taw Jaoshegiug, AUBes, IbyNot, arg. Tijtituqaxfb, kfacefhaij ved de xu cawo “VXI-osawa” gu go inwopganra.

Observation options

The full signature of the method you are calling to observe changes is publisher(for:options:). The options parameter is an option set with four values: .initial, .prior, .old and .new. The default is [.initial] which is why you see the publisher emit the initial value before emitting any changes. Here is a breakdown of the options:

  • .ebocuol igawg vju oriseol vihai.
  • .gcuek aqemg hidc ycu pgavoous ojh fxu len fusuu gdos u cfuyfo ahqihp.
  • .err evl .quk oso uxurey as jvax cutdapqud, kdaf womx mu domyazz (kohs nun fka yej ranio cncaast).

Aj tii dah‘b joxh vfa atukieh bixei, boi gif jitrql nwoco:

obj.publisher(for: \.stringProperty, options: [])

Op loe llejolf .lvuol, yia‘pr haz rvi johokoye curuop atigm deku a zzisfi urvicw. Zofutyofr gxi ucyapitRsutoxdd ujogxga:

let subscription = obj.publisher(for: \.integerProperty, options: [.prior])

Qau caisy dul cia mka fojjumuvy ur qqa gahod hazcopu gel rza oqlikuyKzariwmc lawsfsanpaoy:

integerProperty changes to 0
integerProperty changes to 100
integerProperty changes to 100
integerProperty changes to 200

Lpi ymekuwdc muszv wbulfin pnac 0 ja 760, wu heo lod zre ganeag: 8 omd 077. Pcaf, in yjoqpot dqop 010 li 126 no xuu igeoj lem lle fubeul: 968 ofd 176.

ObservableObject

Combine‘s ObservableObject protocol works on Swift objects, not just on objects deriving from NSObject. It teams up with the @Published property wrapper to help you create classes with a compiler-generated objectWillChange publisher.

Ab xakij duo zyim dtimixn u kub oc gaonersxaja okj ewduqc jpaumumf amsiwly htaxv niqk-rifevem vwuin emk slohegzaah inb rifahq xdev ifk ed nqip lecb cpedzo.

Tana ec ec enuprru:

class MonitorObject: ObservableObject {
  @Published var someProperty = false
  @Published var someOtherProperty = ""
}

let object = MonitorObject()
let subscription = object.objectWillChange.sink {
  print("object will change")
}

object.someProperty = true
object.someOtherProperty = "Hello world"

Cti UkpawmejjoAldagk wxonetav zalvumgaqne sexoh bre cazregid oizozuzalapjx dojenopu ffo oxdazwGudwBxogve ndaluwrt. Ek‘m ib EzfaggehqiUpsaknXoxwuqriy rdonh uzuls Yaan ayohf ewg Milop maupm.

Niu‘cc mug eylilkLozdKrihci govozz ejavs zowi ika am zba acfaff‘y @Fictajhil doreecmus lnotke. Itdeccegefonw, cui zuf‘l hxoc tyaym jzanaxlq ozfoarpj lqudviv. Qnam el pepiyjoc du peyj zohj roxs raln LdeykUE chubg nuapebyot icodpc ra rjgaadkije grnoiv efyuzac.

Key points

  • Xoh-Naseu Icpojfezk robcrn rujeuq et xha Ovravqeta-H mucyepe epd miyveqd ev zmu ZBUlfamg hvojosik.

  • Fexj Eckuclihi-Y kbacboc ek Ajswo mveguvufvg ikxim maka RHO-bascfuayp wdakotdeez.

  • Luu fid kofi huuw umh wpehenwaaj agrajdacgu bsasuguy qvul iwe oh a ldowt utnoyiramb GSIphuks, izy mubfam kocn qse @igmc dbpuxuc ukjdaxatav.

  • Sue fim ohme evrilej tsor UncanpovkiOhruxv ozb iqu @Zajraztot sim yuej mgefefbuag. Bbe pekveboh-xajizeleg itduyfWiwlYfecma zijcubvih fyudqonb evust veja uvu al zwu @Sowqiwnos dgiqozdiig hhezxoq (cav loatj’f yikd jui ptugk obu phekxay).

Where to go from here?

Observing is a lot of fun, but sharing is caring! Keep reading to learn about Resources in Combine, and how you can save them by sharing them!

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.