Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to bind properties between two classes with Combine?

Tags:

swift

combine

How to make a mechanism similar to binding in the view that refers to the Published in the ObservableObject class, but between 2 classes? That is, from class A, call the class B constructor, passing the prop1 from class A. And in class B prop2 exists, and these prop1 and prop2 should mirror each other.

PS: Attached code does not work, just visually show what I want ...

PPS: prop1 in ClassA and prop2 in ClassB, must be Published by condition. It's own views are also subscribed on it.

PPPS: Important to have any binding logic inside the ClassB, for easy reuse.

class ClassA: ObservableObject {
    @Published var prop1: Bool = false //When prop1 changed, it must be reflected in prop2
    let classB: ClassB

    init() {
        classB = .init(prop2: _prop1) //Should stay that simple, no setup here, passing only property, ClassB doesn't know about ClassA.
    }
}

class ClassB: ObservableObject {
    @Published var prop2: Bool //When prop2 changed, it must be reflected in prop1

    init(prop2: Published<Bool>) {
        _prop2 = prop2
    }
}
like image 761
bodich Avatar asked Sep 13 '25 20:09

bodich


1 Answers

A simple but generic solution could look like the following.

Add the following extension:

extension Published.Publisher where Value: Equatable {
    mutating func link(with other: inout Self) {
        removeDuplicates().assign(to: &other)
        other.removeDuplicates().assign(to: &self)
    }
}

This only works with values that conform to Equatable, because otherwise you might end up in an endless loop.

Then call the extension in the initializer of ClassA:

classB.$prop2.link(with: &$prop1)

Original Answer(s)

A simple solution would be to put the following code into your init of ClassA:

$prop1
    .removeDuplicates()
    .assign(to: &classB.$prop2) // mirror changes of prop1 into classB.prop2
classB.$prop2
    .removeDuplicates()
    .assign(to: &$prop1) // mirror changes of classB.prop2 into prop1

However, I'd recommend removing prop1 from ClassA and accessing this value via classB.prop2 and classB.$prop2, respectively. This makes the dependency clearer and doesn't require storing and updating the value in two places.


Answer to question in comments regarding putting the logic in ClassB

To do this, you could add a function in ClassB that performs the linking:

fileprivate func linkProps(to classA: ClassA) {
    $prop2
        .removeDuplicates()
        .assign(to: &classA.$prop1) // mirror changes of prop2 into classA.prop1
    classA.$prop1
        .removeDuplicates()
        .assign(to: &$prop2) // mirror changes of classA.prop1 into prop2
}

And then call this function in the init of ClassA (and wherever else you need to):

init() {
    classB = .init(prop2: _prop1)
    classB.linkProps(to: self)
}

Answer to question in comments regarding keeping ClassB from knowing ClassA

This could be achieved by only passing the property wrapper's projected value into the function on ClassB:

fileprivate func linkProp2(to prop1: inout Published<Bool>.Publisher) {
    $prop2
        .removeDuplicates()
        .assign(to: &prop1) // mirror changes of prop2 into prop1
    prop1
        .removeDuplicates()
        .assign(to: &$prop2) // mirror changes of prop1 into prop2
}

The call in the initializer of ClassA would then look like this:

classB.linkProp2(to: &$prop1)
like image 123
Florian Friedrich Avatar answered Sep 15 '25 12:09

Florian Friedrich