如何在不中断主线程的情况下延迟 swift 中的 for 循环?

How do I delay a for loop in swift without interrupting the main thread?

我试图在继续阅读下一个字符之前延迟 0.1 秒读取一个字符串中的一个字符。

我试过在for循环中实现延迟函数,但它有两个问题。 1. 延迟不一致,在角色之间移动时不会花费相同的时间。 2. 它扰乱了主线程,我认为这是导致相机视图冻结的原因。但是,我认为也有可能在相机开启时激活手电筒会冻结信号,从而导致故障。

func delay(_ delay:Double, closure:@escaping ()->()) {
    DispatchQueue.main.asyncAfter(
        deadline: DispatchTime.now () + Double(Int64(delay * 
    Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: 
    closure)
}

func scriptReader(){
    let str = "10101000001111110000110"
    for i in 0..<str.count {

        delay(Double(i) * 0.5) { 
            let index = str.index(str.startIndex, offsetBy:  i) 
            self.flash(number: str[index]) 
        }
    }
}
func flash(number: Character){
    guard let device = AVCaptureDevice.default(for: 
 AVMediaType.video) else { return }
    guard device.hasTorch else { return }

    if number == "0" {
        print("off")
        do {
            try device.lockForConfiguration()

            if (device.torchMode == AVCaptureDevice.TorchMode.on) {
                device.torchMode = AVCaptureDevice.TorchMode.off
            }
            device.unlockForConfiguration()
        } catch {
            print(error)
        }
    }

    if number == "1"{
        print("on")
        do {
            try device.lockForConfiguration()

            if (device.torchMode == AVCaptureDevice.TorchMode.off) {
                do {
                    try device.setTorchModeOn(level: 1.0)
                } catch {
                    print(error)
                }
            }
            device.unlockForConfiguration()
        } catch {
            print(error)
        }
    }
}

我认为相机死机是因为你每次在for循环中调用它的属性,做一次,将它的属性传递给你的func

试试这个代码来解决你的问题

    func scriptReader(){
    let str = "10101000001111110000110"
    guard let device = AVCaptureDevice.default(for: .video) else { return }
    guard device.hasTorch else { return }
    for i in 0..<str.count {
        DispatchQueue.main.asyncAfter(deadline: .now() + Double(i) * 0.5) {
            let index = str.index(str.startIndex, offsetBy:  i)
            self.flash(number: str[index])
        }
    }
}

func flash(device: AVCaptureDevice, isOn: Bool) {
    do {
        try device.lockForConfiguration()
        device.torchMode = isOn ? AVCaptureDevice.TorchMode.off : device.setTorchModeOn(level: 1.0)
        device.unlockForConfiguration()
    } catch {
        print(error)
    }
}

关于您的第一个问题,使用一系列 asyncAfter 会受到“计时器合并”的影响,其中 OS 将对未来的、单独安排的计时器进行分组以同时触发所有计时器, 以充分利用设备电池(OS 唤醒设备的频率越低,电池寿命越长)。计划的计时器越远,OS 的合并就越多。

可以通过使用重复 Timer:

来避免这种情况
func handle(string: String) {
    guard !string.isEmpty else { return }

    var index = string.startIndex
    Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
        if string[index] == "0" {
            // handle "0"
        } else {
            // handle not "0"
        }

        index = string.index(after: index)
        if index == string.endIndex  { timer.invalidate() }
    }
}

像这样简单的方法也可能有效:

let threadName = "FlashThread"
let string = "10101000001111110000110"

//Create a custom thread
DispatchQueue(label: threadName).async {
    print("Thread Start")

    //Start loop
    string.forEach { (c) in
        print(c)

        //Do something on main thread
        DispatchQueue.main.async {
            self.flash(number: c)
        }

        //Put your thread to sleep for a second
        sleep(1)
    }

    print("Thread END")
}

我现在已经解决了这个问题。第一个问题用 Rob 提供的定时器代码解决了。这停止了​​主线程的冻结,现在代码以一致的时序遍历字符串。 第二个问题通过消除 AVCaptureDevice 的多个实例得到修复,一旦修复,代码就可以工作了。 感谢您的帮助!