以下是有关守则的要点:我们有一些Int @State
,我们希望以秒为间隔倒计时到零,但从函数到自身添加闭包到分派队列似乎不起作用:
func counting(value: inout Int) {
value -= 1
if value > 0 {
// ERROR: Escaping closure captures 'inout' parameter 'value'
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0) {
counting(value: &value)
}
}
}
...
@State private var countdown: Int
...
// kickstarting the countdown works ok
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0) {
counting(value: &countdown)
}
...
这种模式在原则上是错误的吗?如果是,为什么?最简单的正确模式是什么?
4条答案
按热度按时间qc6wkl3g1#
这里有一个非常优雅的答案来回答类似的问题:DispatchQueue.main.asyncAfter not delaying递归模式的用法如下:
在没有State var的情况下调用:
如果你需要一个状态变量来进行倒计时:
在视图中:
希望能帮上忙。
原因如下:Swift 3.0 Error: Escaping closures can only capture inout parameters explicitly by value
icnyk63a2#
这种模式在原则上是错误的吗?
是的。不管看起来如何,
inout
不是通过引用传递。相反,它创建一个副本,然后在函数退出时将其写回原始值。所以DispatchQueue
中发生的任何事情都不会影响原始值。你可以在这里看到更多细节:Swift 3.0 Error: Escaping closures can only capture inout parameters explicitly by value
您尝试实现的一个非常简单的版本是使用每秒触发一次的
TimerPublisher
。在body中,您可以使用.onReceive
修饰符订阅发布者:8yoxcaq73#
如上所述,您无法捕获
inout
参数。这说不通啊inout
参数的工作方式是将其复制到函数中,当函数返回时,将其复制回来。这个函数试图在函数返回后继续操纵它;那是无效的。由于您在这里使用SwiftUI,通常的方法是
.task
:如果你真的想要一个像你描述的那样的函数,递归地修改一个值(这对这个例子来说不是很明智,但它是法律的的),你会使用一个Binding:
你应该这样开场:
c86crjj04#
非常好的答案,我从阅读中学到了很多,并试图了解它们是如何工作的。关键是闭包参数是按值传递的,因此只要状态可以从一个调用流到另一个调用(通过参数),claude 31的解决方案就是一种直接的方法。
序列参数似乎有点大材小用,除非调用的数量相对较少。如果我想倒计时10,000,000次;这意味着具有10 M个元素的序列的第一调用。编译器可能对第一个闭包和后续闭包进行了一些写时复制优化,但我不想对此进行推理。对于我的用例,我修改了它,所以状态(参数大小)是O(1):
我认为这可能并不完美,因为闭包的执行次数没有得到保证,所以连续调用之间的间隔可能不是我想要的。我没有读过DispatQueue的保证,但是连续的闭包可能会以不同的顺序执行(不同于它们被添加的顺序)。如果这是一个问题,那么Timer发布者解决方案听起来更可取,也是一个更好的方法。