当.sink()
块执行时,期望@Published
属性的值已经更新是一个常见的错误。然而,在这种情况下,属性仍然具有旧值,因为.sink()
是由willSet
触发的(如这里所解释的)。
一些人建议,例如这里,添加.receive(on:)
可以解决这个问题。
但是,我也在某个地方读到,添加.receive(on:)
并不是一个根本的解决方案。这使我想到,在某些条件下,它仍然可能失败。
所以,我的问题是:添加.receive(on:)
是否保证.sink()
块将在属性的值实际设置后执行(并且didSet
已被调用)?
下面是上面引用的示例视频中的一些代码。如果没有.receive(on:)
,表中不会显示调用addItems()
后新的["One", "Two", "Three"]
内容;尽管.sink()
块被执行。
override func viewDidLoad()
{
super.viewDidLoad()
viewModel.$dataSource
.receive(on: RunLoop.main) <<=== 'fixes' issue
.sink(receiveValue:
{ [weak self] _ in
self?.tableView.reloadData()
})
}
@IBAction func addItems()
{
viewModel.dataSource = ["One", "Two", "Three"]
}
作为补充,这里是@robmayoff关于使用哪个调度器的稍微相关的答案;另一个常被误解的话题。
在处理异步代码时,"保证"一词有点危险。当您使用receive(on:)
时,您基本上是在说,在将来某个未指定的点,确保该消息在特定上下文中到达订阅者(在本例中为sink
块)。
你有三个执行上下文要考虑。向管道发布的上下文,设置新值的上下文,以及在设置好值后对读取值感兴趣的上下文。
如果发布到管道的上下文和读取该值的上下文不同于设置该值的上下文,receive(on:)
可能会失败。在您的示例中,设置该值的上下文是"主上下文"。如果主线程以外的其他线程写入属性,receive(on:)
将调度一个"set";主线程上的操作。你不能保证这个集合什么时候会真正出现。