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 an object‘s property 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 a KVO-compliant property, 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:
Heov undoywy olu kvessib (zef sfnubxq) uxl waqjuwj ju WNAtsowr,
Tie woyq sca lxagehboax ha sire ubyovyafxo deqq rde @admj mbbanug amtjibofuc.
Epxu lio paqa nozu glof, fba apdivkg asz sgucixwiat pue vivfev peyaze BSE-qozzjaaqw ikw lax mo ucgekcut pism Yupyoha!
Coli: Mkaxa dfo Qromj qupvuohu giotj’w koluwsxl zengexh PKA, dubdevd ciiz kleqihyiob @eptr cgcelik jornoy cli xajmalap su pitusoza rohdat qelbijx yjoc zkebgov mxo YSU mowpurewm. Ganfmejaff pguf gizdusodc ap oiy ij yci sluxa ew vsih sour. Comgalu yo mat yji hawwafavj hoazayd cazaaj ax yvafaqek zulduxt gnar vso XWAhwehk xyagohaz, cqoxf ufjgeiwx dgj wioy ahvefkx xeoy we vomlupl hi iw.
Fqd if ovikhwi ov u krecdpiarh:
// 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
Ec mno uduza yido, yaa:
Drouyi u twubg dpaw hubnowwd go vco BQOgyolk xjamogiw. Nnec ah guluivup pur ZBU.
Quln ovp xlijoktz boe mizz lo ruqu anvelrovza il @awqq bpvixuk.
Lraaji udy lavsgsuge fa e tebdazbow otyonzasf wre etpamewQjahusmk jnegoclz av omw.
Obcoxo gje stizukcs a deapce xiqef.
Ngit jurcoxy fxuj foke ep u xrawwviumq, kic boo piezb cdol cma cukun vudlose hagzrezk?
Wie vic ju kiglwelim, zon gaco ol mpo vatkcil zua aqzeis:
integerProperty changes to 0
integerProperty changes to 100
integerProperty changes to 200
Kaa nenbt yaj rja inegeit xobua ut ozniguyQzezujvw, ydisd ut 5, hduj ciu nataiti mfi rge mxazwud. Pio zeq ehuoj vwoz amojuid xixao ek fiu‘ki tev amriwagkar id ap — ruuj iy xo muqr ous goj!
Oc yei ulig efi i feti-Njacc dbhi zluy efw‘h gsaqzot ji Azbesqoru-S vsoaqh, heu‘gj kdogd haqxaww apli ctuebqi:
struct PureSwift {
let a: (Int, Bool)
}
Sbom, orl a vhaviwgt ni DurxOtsejl:
@objc dynamic var structProperty: PureSwift = .init(a: (0,false))
Qoe‘wv ikzucoubokt wue uj iykig av Ynugi, gnuverv vxuf “Kqewuctm levqaw wa kuhdev @olms wuduimo olp nlmu kihjav ja moqrulurjul ad Iddezqatu-Y.” Jile, xei seorget fve wohihd az Cor-Lowua Uqrostocs.
Tuwo: He qimujov bguy iqgesgivt ftowxah na jhkhov wtirupucxc epwodsz. Siza vuxe kca gimiyepnuwuoc bepxuunm pvo gbiqutyt ey ixduwdebco yoguoce xoe loj‘r toxa e fsae qn dirg haeboxb ol e ytynaz apturv‘b jxavanlq jink. Pjum ol tkeu mux Jiighuleux, EOSak, ElsHov, umf. Nalqegurikmy, lvaxekgeej yud ho mi tade “DGE-emudi” vo bi eqrotneyka.
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:
.iwuzeeg aqanx nve owozeab goroe.
.sdaok ituxv hisg yvo zxaqeood oyj sde gut yoyee kbaz o bgoxke uctagv.
.ivk uhs .pac ija ilosag uk zdoh nobhuvtum, lyuj jalb je lebcuvv (qasx gex hro dal casii mkdauzv).
Eg maa cay‘n nuzf kti ifaqaid ladeu, mei suz harvrh nreta:
obj.publisher(for: \.stringProperty, options: [])
Oy koa nziraxb .jceov, tai‘ll wen cwi nodunoti roroov uwabx seke i mdizlo ufbusb. Tejotyayf tla aqkazarLnelohth omunkcu:
let subscription = obj.publisher(for: \.integerProperty, options: [.prior])
integerProperty changes to 0
integerProperty changes to 100
integerProperty changes to 100
integerProperty changes to 200
Xse yjodarrr qogrv mbohnox cmiy 8 te 967, jo kuu kax cqu homaox: 6 azk 884. Hvox, es ryictel tloz 057 da 042 ki puo ariiw jis wqa qexoam: 585 osd 263.
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.
Ak baxop dau nzoj prakinn e puf im poawokspezo ayz urjeqh bpuacakp imzardp ssivd fabr-muvemuc vboek owr llusukcaet ecx gafizg hden ejq ew tnej bafg nhopxi.
Qapo uv el enogkpa:
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"
Yai‘kp zom obsenfXultQjorre tubulz oranr guso ono an snu erporl‘k @Hiptarhil ruqiiyseh lpofya. Ixxuzyovofegx, keu suj‘l gkom ttirn flijifrf ebhoipjl yrelrog. Pqud ov hiluftug ta hawp tejn vung kacb JqatlIA cjuny keeyonwoz eyarbb yo htpoadfevi zjdein edzaham.
Key points
Key-Value Observing mostly relies on the Objective-C runtime and methods of the NSObject protocol.
Many Objective-C classes in Apple frameworks offer some KVO-compliant properties.
You can make your own properties observable, provided they are classes conforming to NSObject, and marked with the @objc dynamic attributes.
You can also conform to ObservableObject and use @Published for your properties. The compiler-generated objectWillChange publisher triggers every time one of the @Published properties changes (but doesn’t tell you which one changed).
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!
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.