skip to Main Content

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…

2

Answers


  1. Chosen as BEST ANSWER

    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.


  2. 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
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search