问题
I'm subscribing the the built-in User Defaults extension, but it seems to be firing multiple times unnecessarily.
This is the code I'm using:
import Combine
import Foundation
import PlaygroundSupport
extension UserDefaults {
@objc var someProperty: Bool {
get { bool(forKey: "someProperty") }
set { set(newValue, forKey: "someProperty") }
}
}
let defaults = UserDefaults.standard
defaults.dictionaryRepresentation().keys
.forEach(defaults.removeObject)
print("Before: \(defaults.someProperty)")
var cancellable = Set<AnyCancellable>()
defaults
.publisher(for: \.someProperty)
.sink { print("Sink: \($0)") }
.store(in: &cancellable)
defaults.someProperty = true
cancellable.removeAll()
PlaygroundPage.current.needsIndefiniteExecution = true
This prints:
Before: false
Sink: false
Sink: true
Sink: true
Why is it firing the sink 3 times instead of only once?
I can maybe understand it firing on subscribe, which is confusing because it doesn't seem to be a PassthroughSubject
or any documentation of this. However, what really confuses me is the third time it fires.
UPDATE:
It's strange but it seems the initial value gets factored into the new/old comparison:
defaults.someProperty = false
defaults.someProperty = true
defaults.someProperty = false
defaults.someProperty = true
print("Initial: \(defaults.someProperty)")
defaults
.publisher(for: \.someProperty, options: [.new])
.sink { print("Sink: \($0)") }
.store(in: &cancellable)
defaults.someProperty = true
The above will print which looks good:
Initial: true
Sink: true
But when the initial value is different than what you set it to:
defaults.someProperty = false
defaults.someProperty = true
defaults.someProperty = false
defaults.someProperty = true
defaults.someProperty = false
print("Initial: \(defaults.someProperty)")
defaults
.publisher(for: \.someProperty, options: [.new])
.sink { print("Sink: \($0)") }
.store(in: &cancellable)
defaults.someProperty = true
The above will strangely print:
Initial: false
Sink: true
Sink: true
This is untiutive because it's treating the initial value as a trigger of [.new]
, then compares again for what was set.
回答1:
The first published value is the initial value when you subscribe, if you don't want to receive the initial value you can specify this in options (they are NSKeyValueObservingOptions
):
defaults
.publisher(for: \.someProperty, options: [.new])
.sink { print("Sink: \($0)") }
.store(in: &cancellable)
Every new value is indeed published twice, but you can just remove duplicates:
defaults
.publisher(for: \.someProperty, options: [.new])
.removeDuplicates()
.sink { print("Sink: \($0)") }
.store(in: &cancellable)
Which will give you the behaviour you want.
UPDATE:
if you define your extension like this:
extension UserDefaults {
@objc var someProperty: Bool {
bool(forKey: "someProperty")
}
}
and then set the value using:
defaults.set(false, forKey: "someProperty")
The values are published only once.
来源:https://stackoverflow.com/questions/65965666/why-does-user-defaults-publisher-trigger-multiple-times