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.