'Variables dependency using Combine
I have a class with two variables depending on each other. If one variable changes it should change the other and vice versa.
I am using the Combine framework in iOS Swift. So with sink I am listening/subscribe to changes of the publisher.
var store = Set<AnyCancellable>()
class Obs: ObservableObject {
@Published var pub: Int = 0
}
let ob1 = Obs()
let ob2 = Obs()
ob1.$pub
.dropFirst() // ignore initial assignment
.sink { num in
if ob2.pub != num {
ob2.pub = num
}
}.store(in: &store)
ob2.$pub
.dropFirst()
.sink { num in
if ob1.pub != num {
ob1.pub = num
}
}.store(in: &store)
ob1.pub = 1
With this code I get a stack overflow. How can I break the "infinite loop"?
Should I use something like semaphores? Or some kind of flag within a tuple? Or are there some special combine filter? Or any other ideas...?
I think I may have some general misunderstanding of the problem...
Solution 1:[1]
The "problem" is that publishers have willSet semantics, i.e. the pub variable will first publish the new value and then the variable pub will change its value. So the condition ob1.pub != num that reads the ob1 value will never be false.
In order to send the value after the variable has been updated you modify the publisher to receive(on:) a scheduler:
ob1.$pub
.receive(on: RunLoop.main)
.dropFirst()
.sink { num in
if ob2.pub != num {
ob2.pub = num
}
}.store(in: &store)
ob2.$pub
.receive(on: RunLoop.main)
.dropFirst()
.sink { num in
if ob1.pub != num {
ob1.pub = num
}
}.store(in: &store)
ob1.pub = 1
Solution 2:[2]
I got a workaround with the Combine operators scan and compactMap. So I can compare the new value with the previous/old one. If the new value and the old value are equal the published value is transformed to nil and dropped/ignored with compactMap.
var store = Set<AnyCancellable>()
class Obs: ObservableObject {
@Published var pub: Int = 0
}
let ob1 = Obs()
let ob2 = Obs()
ob1.$pub
// new >>>
.scan(ob1.pub) {
$0 == $1 ? nil : $1
}
.compactMap { $0 }
// <<<
.sink { num in
if ob2.pub != num {
ob2.pub = num
}
}.store(in: &store)
ob2.$pub
// new >>>
.scan(ob2.pub) {
$0 == $1 ? nil : $1
}
.compactMap { $0 }
// <<<
.sink { num in
if ob1.pub != num {
ob1.pub = num
}
}.store(in: &store)
This helped me to break the loop in my case.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Christos Koninis |
| Solution 2 | Laufwunder |
