Home > database >  UserDefaults keypath publisher doesn't fire beyond first value
UserDefaults keypath publisher doesn't fire beyond first value

Time:11-08

Given a obj-c keypath

@objc dynamic var someProp: String { string(forKey: "someProp") }

A regular publisher:

private let sub = UserDefaults.standard.publisher(for: \.someProp).sink { print($0) }

This publishes only works for the first value (e.g. the current value).

However observing the sub publisher from SwiftUI works fine:

.onReceive(pub) { value in
    print("received", value)
}

This publishes any subsequent updates.

Any ideas why the former doesn't work?

Edit: Here is a minimal reproducible example:

public extension UserDefaults {
    @objc dynamic var value1: Int {
        integer(forKey: "string1")
    }
}

struct ContentView: View {
    @StateObject var vm = ViewModel()

    private let pub = UserDefaults.standard.publisher(for: \.value1)

    var body: some View {
        Button("Add") {
            var value = UserDefaults.standard.value(forKey: "value1") as? Int ?? 0
            value  = 1
            debugPrint("SET", value)
            UserDefaults.standard.set(value, forKey: "value1")
        }
        .onReceive(pub) { value in
            debugPrint("UI", value)
        }
    }

    class ViewModel: ObservableObject {
        private let sub = UserDefaults.standard.publisher(for: \.value1).sink {
            debugPrint("SUB", $0)
        }
    }
}

CodePudding user response:

The error here is how you access and assign your values in the Button action. You are setting the values for the key value1. But the publisher observes the key string1 with the dynamic var named value1.

TLDR: You confused the dynamic var with your key

I would recommend you ommit the access via .value(forKey: "") and use only your dynamic var.

public extension UserDefaults {
    @objc dynamic var value1: Int {
        // add getter and setter
        get{
            integer(forKey: "string1")
        }
        set{
            set(newValue, forKey: "string1")
        }
    }
}

struct ContentView: View {
    @StateObject var vm = ViewModel()

    private let pub = UserDefaults.standard.publisher(for: \.value1)

    var body: some View {
        Button("Add") {
            //here
            UserDefaults.standard.value1  = 1
            debugPrint("SET", UserDefaults.standard.value1)
        }
        .onReceive(pub) { value in
            debugPrint("UI", value)
        }
    }

    class ViewModel: ObservableObject {
        private let sub = UserDefaults.standard.publisher(for: \.value1).sink {
            debugPrint("SUB", $0)
        }
    }
}

Prints:

"SUB" 0
"UI" 0
"SET" 1
"SUB" 1
"UI" 1
"SET" 2
"SUB" 2
"UI" 2
"SET" 3
"SUB" 3
"UI" 3
  • Related