Search code examples
swiftcombine

Get reference for an @Published that can update the value


I'm trying to dynamically update a reference to an @Published var, but I'm not sure how to do it. Returning the value only is just a bool value and loses it's reference to the publisher and doesn't work. I tried returning the publisher itself ($self.isBig) but I can't seem to figure out how to update the value once I have the publisher instead of the @Published property.

I'm basically just trying to treat an @Published as a reference and update the reference on the class instead of copying the publisher's value.

This is a contrived example just trying to get the point across, what I want to do is:

import UIKit
import Combine

class MyClass {
    
    struct MyData {
        enum MyDataType {
            case big
            case yellow
            case bird
        }
        var type: MyDataType
        var isEnabled: Bool
    }
    
    private var cancellables = Set<AnyCancellable>()
    
    @Published var isBig: Bool = false
    @Published var isYellow: Bool = false
    @Published var isBird: Bool = false
    
    var mySwitch = UISwitch()
    
    init() {
        // Add mySwitch and setupSubscribers...
    }

    func updatePublishers(from data: MyData) {
        let publisherForData = specificPublisherForData(data)
        // I want to access the underlying value for the @Published and set it here
//        publisherForData = data.isEnabled
    }

    func specificPublisherForData(_ data: MyData) -> Published<Bool>.Publisher {
        switch data.type {
        case .big: return self.$isBig
        case .yellow: return self.$isYellow
        case .bird: return self.$isBird
        }
     }
    
    func setupSubscribers() {
        $isBig.sink { [weak self] isBig in
            guard let self = self else { return }
            
            self.mySwitch.isOn = isBig
        }.store(in: &cancellables)
        
        // ... add subscribers for the other ones
    }
}

Solution

  • It looks like you're trying to assign a new value to the Publisher in updatePublishers, but that's not really how @Publishers work -- they're just broadcasting values.

    Instead, it seems like using a key path might be what you're after:

    class MyClass {
        
        struct MyData {
            enum MyDataType {
                case big
                case yellow
                case bird
            }
            var type: MyDataType
            var isEnabled: Bool
        }
        
        private var cancellables = Set<AnyCancellable>()
        
        @Published var isBig: Bool = false
        @Published var isYellow: Bool = false
        @Published var isBird: Bool = false
        
        var mySwitch = UISwitch()
        
        init() {
            // Add mySwitch and setupSubscribers...
        }
    
        func updatePublishers(from data: MyData) {
            let keypath = specificKeypathForData(data)
            self[keyPath: keypath] = data.isEnabled
        }
    
        func specificKeypathForData(_ data: MyData) -> ReferenceWritableKeyPath<MyClass,Bool> {
            switch data.type {
            case .big: return \.isBig
            case .yellow: return \.isYellow
            case .bird: return \.isBird
            }
         }
        
        func setupSubscribers() {
            $isBig.sink { [weak self] isBig in
                guard let self = self else { return }
                
                self.mySwitch.isOn = isBig
            }.store(in: &cancellables)
            
            // ... add subscribers for the other ones
        }
    }
    

    In a playground:

    var myClass = MyClass()
    myClass.setupSubscribers()
    var data = MyClass.MyData(type: .big, isEnabled: true)
    myClass.updatePublishers(from: data)
    print(myClass.isBig)
    

    Yields:

    true