Search code examples
swiftiphoneflashlight

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


I am trying to read through a string one character a time delaying .1 seconds before moving on to the next character.

I have tried implementing the delay function inside the for loop but it has two problems. 1. The delay is inconsistent and does not take the same amount of time when moving between characters. 2. It disrupts the main thread which I think is the cause of the camera view freezing. However, I think it is also possible that activating the flashlight while the camera is on freezes the signal, causing the glitch.

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)
        }
    }
}

Solution

  • Re your first concern, the use of a series of asyncAfter are going to suffer from “timer coalescing”, where the OS will group future, individually scheduled timers to fire all at once, to make best use of the device battery (the less often the OS needs to wake up the device, the better the battery life). The further out the scheduled timers are, the more coalescing the OS will do.

    One can avoid this by using a repeating 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() }
        }
    }