为什么我不能在调用 func 时改变最初设置为某个参数的变量? [英] Why can't I mutate a variable initially set to a certain parameter when the func was called?

查看:39
本文介绍了为什么我不能在调用 func 时改变最初设置为某个参数的变量?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

目标:我正在尝试创建一个通用结构,它可以接受一组 Int 并依次为每个 Int 设置一个计时器(并显示一个屏幕).

GOAL: I'm trying to make a general struct that can take an array of Ints and go through and set a timer for each one (and show a screen) in succession.

问题:我收到 Escapingclosure captures mutating 'self' parameter 错误,如代码所示.

Problem: I get Escaping closure captures mutating 'self' parameter error as shown in the code.

import SwiftUI

struct ContentView: View {

    @State private var timeLeft = 10
    @State private var timers = Timers(timersIWant: [6, 8, 14])
//    var timersIWantToShow: [Int] = [6, 8, 14]

    var body: some View {
        Button(action: {self.timers.startTimer(with: self.timeLeft)}) {
            VStack {
                Text("Hello, World! \(timeLeft)")
                    .foregroundColor(.white)
                    .background(Color.blue)
                    .font(.largeTitle)
            }
        }
    }

    struct Timers {

        var countDownTimeStart: Int = 0
        var currentTimer = 0
        var timersIWant: [Int]

        mutating func startTimer(with countDownTime: Int) {

            var timeLeft = countDownTime

            Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { timer in  //Escaping closure captures mutating 'self' parameter


                if timeLeft > 0 {
                    timeLeft -= 1
                } else {
                    timer.invalidate()
                    self.currentTimer += 1
                    if self.currentTimer < self.timersIWant.count {
                        self.startTimer(with: self.timersIWant[self.currentTimer])
                    } else {
                        timer.invalidate()
                    }
                }
            })
        }
    }

}

我不确定这是否与我的 recursvie 函数有关(也许这是不好的形式?)我猜 escapingclosurefunc startTimer 并且有问题的 'self' 参数countDownTime 参数,但我不确定发生了什么或为什么会出错.

I'm not sure if this has to do with my recursvie function (maybe this is bad form?) and I'm guessing the escaping closure is the func startTimer and the offending the 'self' parameter is the countDownTime parameter, but I'm not really sure what is happening or why it's wrong.

推荐答案

正如 Gil 所指出的,这需要是一个类,因为您将其视为引用类型.当您修改 currentTimer 时,您不会期望创建一个全新的 Timers 实例,这是值类型 (struct) 发生的情况.您希望它修改现有的 Timers 实例.那是一个引用类型(类).但要使这项工作发挥作用,您还需要更多.您需要将计时器绑定到视图,否则视图不会更新.

As Gil notes, this needs to be a class because you are treating it as a reference type. When you modify currentTimer, you don't expect that to create a completely new Timers instance, which is what happens with a value type (struct). You expect it to modify the existing Timers instance. That's a reference type (class). But to make this work, there's quite a bit more you need. You need to tie the Timers to the View, or the View won't update.

IMO,解决此问题的最佳方法是让计时器跟踪当前 timeLeft 并让视图观察它.我还添加了一个 isRunning 已发布值,以便视图可以根据该值重新配置自身.

IMO, the best way to approach this is let Timers track the current timeLeft and have the view observe it. I've also added an isRunning published value so that the view can reconfigure itself based on that.

struct TimerView: View {

    // Observe timers so that when it publishes changes, the view is re-rendered
    @ObservedObject var timers = Timers(intervals: [10, 6, 8, 14])

    var body: some View {
        Button(action: { self.timers.startTimer()} ) {
            Text("Hello, World! \(timers.timeLeft)")
            .foregroundColor(.white)
            .background(timers.isRunning ? Color.red : Color.blue) // Style based on isRunning
            .font(.largeTitle)
        }
        .disabled(timers.isRunning)   // Auto-disable while running
    }
}

// Timers is observable
class Timers: ObservableObject {

    // And it publishes timeLeft and isRunning; when these change, update the observer
    @Published var timeLeft: Int = 0
    @Published var isRunning: Bool = false

    // This is `let` to get rid of any confusion around what to do if it were changed.
    let intervals: [Int]

    // And a bit of bookkeeping so we can invalidate the timer when needed
    private var timer: Timer?

    init(intervals: [Int]) {
        // Initialize timeLeft so that it shows the upcoming time before starting
        self.timeLeft = intervals.first ?? 0
        self.intervals = intervals
    }

    func startTimer() {
        // Invalidate the old timer and stop running, in case we return early
        timer?.invalidate()
        isRunning = false

        // Turn intervals into a slice to make popFirst() easy
        // This value is local to this function, and is captured by the timer callback
        var timerLengths = intervals[...]

        guard let firstInterval = timerLengths.popFirst() else { return }

        // This might feel redundant with init, but remember we may have been restarted
        timeLeft = firstInterval

        isRunning = true

        // Keep track of the timer to invalidate it elsewhere.
        // Make self weak so that the Timers can be discarded and it'll clean itself up the next
        // time it fires.
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in
            guard let self = self else {
                timer.invalidate()
                return
            }

            // Decrement the timer, or pull the nextInterval from the slice, or stop
            if self.timeLeft > 0 {
                self.timeLeft -= 1
            } else if let nextInterval = timerLengths.popFirst() {
                self.timeLeft = nextInterval
            } else {
                timer.invalidate()
                self.isRunning = false
            }
        }
    }
}

这篇关于为什么我不能在调用 func 时改变最初设置为某个参数的变量?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆