zouritre
zouritre

Reputation: 655

Assign Combine publisher to variables using the new SwiftUI Observable framework

SwiftUI 5 now uses the @Observable wrapper to observe changes in a class instead of conforming it to ObservableObject. This gets rid of @Published wrappers which I actively use in my workflow:

Old implementation

class OldObservable: ObservableObject {
    
    let model = SomeModel()
    
    @Published var someValue: String?
    
    init() {
        setupSubscribers()
    }
    
    func setupSubscribers() {
        // someValuePublisher could be PassthroughSubject<String?, Never>()
        model.someValuePublisher.assign(to:$someValue)
    }
}

New implementation

@Observable
class NewObservable {
    
    let model = SomeModel()
    
    var someValue: String?
    
    init() {
        setupSubscribers()
    }
    
    func setupSubscribers() {
        // Not working anymore
        model.someValuePublisher.assign(to:$someValue)
    }
}

Since someValue is not a published property, I can't use the usual assign(to:) anymore.

So far to make it work I have to use the old implementation assign(to:on) which I heard can lead to retain cycle issues and it's longer code for the same result.

    var subscribers = Set<AnyCancellable>()
    func setupSubscribers() {
        model.someValuePublisher.assign(to: \.self.someValue, on: self).store(in: &subscribers)
    }

I could also use the sink(ReceivedValue:) but it also lengthen the code. So is there a new and concise way to bind Combine publishers to the new Observable framework?

Upvotes: 15

Views: 1250

Answers (1)

Chris So
Chris So

Reputation: 833

I found a way to solve the memory leak problem everywhere by simply overriding assign(to:):


// for publisher<XXX, Never>
extension Publisher where Failure == Never {
    func assign<Root: AnyObject>(to keyPath: ReferenceWritableKeyPath<Root, Output>, on root: Root) -> AnyCancellable {
        sink { [weak root] in
            root?[keyPath: keyPath] = $0
        }
    }
}

// For publisher<XXX, Error>
extension Publisher where Failure == Error {
    func assign<Root: AnyObject>(to keyPath: ReferenceWritableKeyPath<Root, Output>, on root: Root) -> AnyCancellable {
        sink { [weak root] in
            root?[keyPath: keyPath] = $0
        }
    }
}

Example:

var subscribers = Set<AnyCancellable>()
func setupSubscribers() {
    // Same as before
    model.someValuePublisher.assign(to: \.someValue, on: self).store(in: &subscribers)
}

reference:

Upvotes: 0

Related Questions