为啥用户默认发布者多次触发

Posted

技术标签:

【中文标题】为啥用户默认发布者多次触发【英文标题】:Why does User Defaults publisher trigger multiple times为什么用户默认发布者多次触发 【发布时间】:2021-01-30 07:30:53 【问题描述】:

我订阅了内置的用户默认扩展,但它似乎不必要地触发了多次。

这是我正在使用的代码:

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

打印出来:

Before: false
Sink: false
Sink: true
Sink: true

为什么它会触发接收器 3 次而不是只触发一次?

我也许可以理解它在订阅时触发,这令人困惑,因为它似乎不是PassthroughSubject 或任何有关此的文档。然而,真正让我困惑的是它第三次触发。

更新:

这很奇怪,但似乎初始值被考虑到新/旧比较中:

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

上面会打印出看起来不错的:

Initial: true
Sink: true

但是当初始值与你设置的不同时:

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

上面会奇怪打印:

Initial: false
Sink: true
Sink: true

这是不合理的,因为它将初始值视为[.new] 的触发器,然后再次比较设置的内容。

【问题讨论】:

这发生在单元测试中并导致我的应用出现问题,我将其提取到 Playground 以获得一些帮助。 如果我删除了之前的删除对象部分,如果开火两次而不是 3 次。这很奇怪,因为删除对象出现在订阅之前。就好像它正在重播一切。这是一个什么样的发布者,我怎样才能直观地工作,这个例子真的应该对我触发一次。 ***.com/questions/60386000/…可能重复 感谢您链接该问题!这确实从不同的角度更清楚地说明了这种行为。 【参考方案1】:

第一个发布的值是订阅时的初始值,如果不想收到初始值可以在options中指定(它们是NSKeyValueObservingOptions):

defaults
    .publisher(for: \.someProperty, options: [.new])
    .sink  print("Sink: \($0)") 
    .store(in: &cancellable)

每个新值确实会发布两次,但您可以删除重复项:

defaults
    .publisher(for: \.someProperty, options: [.new])
    .removeDuplicates()
    .sink  print("Sink: \($0)") 
    .store(in: &cancellable)

这会给你想要的行为。

更新:

如果你这样定义你的扩展:

extension UserDefaults 
    
    @objc var someProperty: Bool 
        bool(forKey: "someProperty")
    

然后使用以下方法设置值:

defaults.set(false, forKey: "someProperty")

这些值只发布一次。

【讨论】:

不是在那之后发布两次,莫名其妙在订阅前所做的更改,后来由于某种原因触发了它。 如果初始值与您设置的值不同,它似乎会触发两次。因此,如果您设置了[.new],从false 开始,然后设置为true,它将触发第一个新值,然后第二次表示新值已更改。非常混乱和奇怪。当设置为[.new] 时,我希望在订阅发生后获得新值,而不是在将初始值与第一组进行比较时。 removeDuplicates 确实解决了它,但作为一种解决方法。 我不太确定。如果您在defaults.someProperty = true 下添加defaults.someProperty =false - 在您的初始设置中,它会给出假/真/真/假/假。这一切都非常令人惊讶。 我在原始问题中添加了一个额外的示例。至少有一些疯狂的一致性,但如果初始值与随后的第一个设置值不同,则初始值触发不是很直观。 这很奇怪,就好像它重复了两次这样你才能得到消息;-)

以上是关于为啥用户默认发布者多次触发的主要内容,如果未能解决你的问题,请参考以下文章

为啥 jQuery 选择事件侦听器会触发多次?

为啥在使用 Caliburn Micro Conductor.OneActive 时,Blend Interaction 事件触发器会多次触发?

为啥我的信号处理程序(引发异常)不会多次触发?

如果我每次都从不同的线程调用事件,为啥会从同一个线程触发事件处理程序的多次执行?

jquery为啥触发多次click事件

ALAssetLibraryChangedNotification 触发多次