转义闭包捕获 Swift 中的变异“self”参数错误
Posted
技术标签:
【中文标题】转义闭包捕获 Swift 中的变异“self”参数错误【英文标题】:Escaping closure captures mutating 'self' parameter Error in Swift 【发布时间】:2020-06-28 22:37:14 【问题描述】:创建一个简单的纸牌游戏(Set),我在模型中有一个函数可以将 X 牌发到牌组上。目前,当我单击交易卡按钮时,它们都会同时显示,因此我添加了计时器,以便它们一个接一个地出现。这给出了错误“转义闭包捕获变异'self'参数”关于我可以修复什么的任何想法?
mutating func deal(_ numberOfCards: Int)
for i in 0..<numberOfCards
Timer.scheduledTimer(withTimeInterval: 0.3 * Double(i), repeats: false) _ in
if deck.count > 0
dealtCards.append(deck.removeFirst())
【问题讨论】:
您应该仅在更新 UI 时使用计时器。做你的洗牌/交易,一旦你完成了调用一个只更新 UI 的方法 我会使用 UIView 动画而不是计时器 "让它们一个接一个地出现" 这是视图的工作,而不是模型的工作。模型应该保持不变。将计时器代码移动到您的视图中。 @Sweeper 这就是我想要做的,但我发现这样做的唯一方法是让 View 执行 ForLoop,这会在代码的后面产生问题。有没有办法让模型中的交易函数将所有 X 卡添加到数组中,然后视图将每张卡一张一张地添加? @LeoDabus 如果通过将卡片添加到模型中的数组来完成发牌和洗牌,我如何使它们全部单独动画。除非从视图中调用 dealcard 12。 【参考方案1】:甚至不需要计时器。您可以将过渡与动画结合使用以获得所需的效果。这里的过渡是根据卡片的索引延迟的:
class CardModel: ObservableObject
@Published var cards: [Int] = []
func deal(_ numberOfCards: Int)
cards += (cards.count ..< (cards.count + numberOfCards)).map $0
func clear()
cards = []
struct ContentView: View
@ObservedObject var cardModel = CardModel()
var body: some View
GeometryReader geometry in
VStack
HStack
ForEach(0 ..< self.cardModel.cards.count, id: \.self) index in
CardView(cardNumber: self.cardModel.cards[index])
.transition(.offset(x: geometry.size.width))
.animation(Animation.easeOut.delay(Double(index) * 0.1))
Button(action: self.cardModel.deal(2) )
Text("Deal")
Button(action: self.cardModel.clear() )
Text("Clear")
struct CardView: View
let cardNumber: Int
var body: some View
Rectangle()
.foregroundColor(.green)
.frame(width: 9, height: 16)
或者更简单一点(没有 CardModel):
struct ContentView: View
@State var cards: [Int] = []
func deal(_ numberOfCards: Int)
cards += (cards.count ..< (cards.count + numberOfCards)).map $0
func clear()
cards = []
var body: some View
GeometryReader geometry in
VStack
HStack
ForEach(0 ..< self.cards.count, id: \.self) index in
CardView(cardNumber: self.cards[index])
.transition(.offset(x: geometry.size.width))
.animation(Animation.easeOut.delay(Double(index) * 0.1))
Button(action: self.deal(2) )
Text("Deal")
Button(action: self.clear() )
Text("Clear")
struct CardView: View
let cardNumber: Int
var body: some View
Rectangle()
.foregroundColor(.green)
.frame(width: 9, height: 16)
注意如果您将卡片居中,这种方法可以正常工作,因为之前的卡片也需要移动。但是,如果您将卡片左对齐(使用间隔),则不需要移动的卡片会出现动画延迟(动画以尴尬的延迟开始)。如果您需要考虑这种情况,则需要使新插入的索引成为模型的一部分。
【讨论】:
这与原始代码有点不同。原来的deal(_:)
允许您将卡片添加到现有手牌中。这个版本总是用给定尺寸的全新手来替换手。如果我没看错的话,如果你切换到append
而不是替换,我认为这种动画方法不会起作用。
@RobNapier 不错。我刚试过:它有点适用于追加,但我不确定效果是否是 OP 想要的。
@RobNapier 我同意可识别结构是 ForEach 循环中更好的方法,但我仍然需要索引来计算动画延迟。
(不需要最后的评论,id
参数处理)【参考方案2】:
(这可能是在重复 Jack Goossen 写的内容;先去看看,如果不清楚,这可能会给出更多解释。)
这里的核心问题是您似乎将结构视为引用类型。结构是一个值。这意味着它的每个持有者都有自己的副本。如果您将值传递给 Timer,则 Timer 正在改变它自己的该值的副本,它不能是 self
。
在 SwiftUI 中,模型通常是引用类型(类)。它们代表了一种可识别的“事物”,可以观察到并随着时间的推移而变化。将此类型更改为类可能会解决您的问题。 (请参阅 Jack Goossen 的答案,它使用一个类来持有卡片。)
这与 Swift 与 UIKit 的发展方向背道而驰,在 UIKit 中,视图是引用类型,并且鼓励模型由值类型构成。在 SwiftUI 中,视图是结构体,模型通常由类组成。
(使用 Combine 和 SwiftUI,可以将 view 和 model 都变成值类型,但这可能超出了您在这里尝试做的事情,如果您还没有学习过 combine 或响应式编程,那就有点复杂了.)
【讨论】:
非常有见地。只是为了挑剔:您也可以在 ForEach 循环中使用带有值类型的动画方法,只要它们是可识别的(我的答案中的索引)。使用 ObservedObject iso 状态更多的是一种习惯,而不是有意的选择。 ForEach 循环可以超过值类型,但被观察的对象必须是一个类(这是应用append
的东西)。以上是关于转义闭包捕获 Swift 中的变异“self”参数错误的主要内容,如果未能解决你的问题,请参考以下文章