How to create multiple delays using a dispatch queue? - swift

Is it possible to use DispatchQueue to create multiple delays in my code?
For example, with sleep I can do:
for index in 1...20 {
do {
sleep(1)
// Code I want to run every 1 second for 20 times
}
}
completionHandler()
But I want to use DispatchQueue instead:
for index in 1...20 {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
// Code I want to run every 1 second for 20 times
}
}
completionHandler()
which doesn't seem to be working (it's not waiting 1 second each time)
Edit:
At the end of running the code for 20 times, I want to call the completionHandler to return a result of the code in the loop. Problem is that this does not wait for the DispatchQueue

// Code I want to run every 1 second for 20 times
What you're looking for is a Timer.
https://developer.apple.com/documentation/foundation/timer
var timer : Timer?
var times = 0
#objc func fired(_ t:Timer) {
times += 1
print("do your code") // do your code here
if times == 20 {
t.invalidate()
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.timer = Timer.scheduledTimer(timeInterval: 1, target: self,
selector: #selector(fired), userInfo: nil,
repeats: true)
}
You will see that print("do your code") runs every 1 second 20 times.

You need to add a different delay Double(index)
for index in 1...20 {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(index)) {
// Code I want to run every 1 second for 20 times
}
}

Related

Swift Run the block of Timer without timer in first time

I created a timer and set it to repeat every 30 seconds
the timer waits 30 seconds to run the first time, then another 30 seconds for the second time, I wanted it to run the first time without the 30 seconds, is there any way?
Timer.scheduledTimer(withTimeInterval: 30, repeats: true) { timer in
if iteration >= 0 {
runCommands()
iteration -= 1
if iteration == 0 { exit(0) }
}
}
Just call fire. It does what the name implies.
Timer.scheduledTimer(withTimeInterval: 30, repeats: true) { timer in
if iteration >= 0 {
runCommands()
iteration -= 1
if iteration == 0 {exit(0)}
}
}.fire()
The problem is that you have no reference to your timer. Make one!
let timer = Timer.scheduledTimer...
Now in the next line you can say timer.fire(). This causes the timer to execute its block immediately.
Either refactor the code that's called from the timer closure to be a function, or put the closure into a variable. Then invoke the function/closure explicitly:
typealias TimerClosure = (_: Timer?) -> Void
let timerClosure: TimerClosure = { [weak self] timer in
guard let self = self else { return }
if self.iteration >= 0 {
self.runCommands()
self.iteration -= 1
if self.iteration == 0 {exit(0)}
}
}
Timer.scheduledTimer(withTimeInterval: 30, repeats: true, block: timerClosure )
timerClosure(nil)
}
Edit:
The approach above will work, but as others pointed out, you can also call fire() on your timer manually.
I'll leave the above part of the answer since it shows how to set up a local variable containing a closure and then both pass it to a function and call it directly, but that's not really needed here. Instead, just do this:
let timer = Timer.scheduledTimer(withTimeInterval: 30, repeats: true) { [weak self] timer in
guard let self = self else { return }
if self.iteration >= 0 {
self.runCommands()
self.iteration -= 1
if self.iteration == 0 { exit(0) }
}
}
timer.fire()

Why is my timer function having irregular intervals?

I have attached my code for you to see.
What I want to achieve:
Timer repeats 5 times
Hardcoded time of 10s
the display of a number (i) reduces by 1 with each iteration
What happens:
i goes straight to 1
countdown goes something like 5, 0, -2, -4, -8, -13 NOT 10,9,8,7,6,5,4,3,2,1,0 then repeat
var timer = Timer()
var i = 5
// number of repeats is 5
var time = 10
// timer is 10s
class ViewController: UIViewController {
#IBOutlet weak var displayTime: UILabel!
#IBOutlet weak var displayI: UILabel!
#IBAction func startButton(_ sender: Any) {
for _ in 1...5 {
displayI.text = String(i)
countdown()
i -= 1
}
}
func countdown() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(Action), userInfo: nil, repeats: true)
}
#objc func Action() {
time -= 1
displayTime.text = String(time)
if time == 0 {
timer.invalidate()
}
}
Can anyone help?
(I also tried a repeat while loop where it repeats while (i >= 1) so it should stop when i=0)
Remember that loops (any kind) runs very quickly. Your for _ in 1...5 loop completes before the first call to Action even starts. This is because countdown() completes almost immediately, not after the timer it creates has been invalidated.
When you create a timer, it runs asynchronously to the rest of your code. So the line immediately after countdown() is not when the timer will end. The line in the if time == 0 statement is.
Note that even if the timer were synchronous, your code wouldn't have worked because you didn't reset time to 10 when each timer ends.
Anyway, the five timers will decrement time almost at the same time, and depending on when the UI refreshes, you will see that time has decremented a different amount.
You seem to just want to count from 10 to 0 five times. You can do this with just one timer:
#objc func Action() {
time -= 1
displayTime.text = String(time)
if time == 0 {
i -= 1
time = 11
}
if i == 0 {
timer.invalidate()
}
}
#IBAction func startButton(_ sender: Any) {
countdown()
}
i counts how many times this is, and time counts the number to display.

How to add DispatchQueue delay in swift while loop?

I'm trying to create a delay inside a while loop. I'm fairly new to this and it's currently just not working. It never fires even once with the dispatch delay, but if I remove the delay it fires repeatedly.
Basically what I'm doing is checking if the velocity of nodes in a SKScene is still moving, if they're still moving, don't end the game. But once they've slowed down, end the game.
func RemainingNodeCheck (complete:() -> Void) {
CountVelocites()
if (IdleVelocity.max()!.isLess(than: 1.0)) {
complete()
} else {
print("Velocity too high, begin wait...")
while !(IdleVelocity.max()?.isLess(than: 1.0))! {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + .seconds(1)) {
print("Second passed")
self.CountVelocites()
}
if (IdleVelocity.max()!.isLess(than: 1.0)) {
break
}
}
print("Velocity Calmed down")
complete()
}
}
I believe this might be something to do with threads? Or it's actually just telling the delay to begin waiting for one second so many times that it never gets to call?
UPDATE: I would use a timer, but the RemaingNodeCheck is being called from another part and it's waiting for RemainingNodeCheck to send back complete()
You never want to "wait". But you can set up a repeating timer that checks for some condition, and if so, calls the complete closure (invalidating the timer, if you want). E.g.
class ViewController: UIViewController {
var idleVelocity: ...
weak var timer: Timer?
deinit {
timer?.invalidate()
}
func startCheckingVelocity(complete: #escaping () -> Void) {
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in
guard let self = self, let maxVelocity = self.idleVelocity.max() else { return }
if maxVelocity < 1 {
timer.invalidate()
complete()
return
}
print("velocity too high...")
}
}
}

Wait itself within endless loop, but user can cancel every time

In Swift 3 I have a loop which can be canceled by user pressing a button. Within the loop some checks are made. After the check, the task can sleep for a minute. But when calling the task with
let delayQueue = DispatchQueue(label: "com.myApp.queue3", qos: .utility)
let additionalTime: DispatchTimeInterval = .seconds(3)
repeat {
delayQueue.asyncAfter(deadline: .now() + additionalTime) { self.update() }
} while !self.stop
the loop itself needs to run all the time waiting for the user
"stop", indicates, that user clicked on stop button.
Is that waste of CPU power? How could I avoid this loop to be done all the time?
You should use Timer instead.
var timer: Timer?
let timeInterval: TimeInterval = 3
func didPressCancelButton() {
timer?.invalidate()
}
func beginUpdates() {
timer = Timer.scheduledTimer(
timeInterval: timeInterval,
target: self,
selector: #selector(self.update),
userInfo: nil,
repeats: true
);
}
func update() {
print("Updated")
}
Instead of delaying execution in thread with an outer loop you can put your loop in thread instead and make it to sleep.
import Foundation
class YourUpdatingClass {
private let updateQueue: OperationQueue
init() {
updateQueue = OperationQueue()
updateQueue.name = "com.myApp.queue3"
updateQueue.qualityOfService = .utility
}
private var updateOperation: BlockOperation?
#IBAction func startUpdating() {
guard updateOperation == nil else {
// In case if updating already started
return
}
updateOperation = BlockOperation { [weak self] in
while true {
Thread.sleep(forTimeInterval: 3)
self?.update()
}
}
updateQueue.addOperation(updateOperation!) // we just created updateOperation, so we can use `!`, but use it with caution
}
#IBAction func stopUpdating() {
updateOperation?.cancel()
updateOperation = nil
}
private func update() {
print("update") // Whatever your update does
}
}
You updating is contained in eternal while loop which takes a nap every 3 seconds.
Stopping is managed by cancelling operation, instead of checking some var in the loop.

Swift: Issue with my NSTimer?

So in general terms, this is what i want my program to do: music will play, randomly generate a number between 3 and 12, then set a timer according to that number, when the timer is finished stop the music for 1 second, then after that one second, play the music again(and repeat the process, generate random number...)
This is the code I have written to do so, it works great the first run, but the second run it starts calling the functions at a faster rate(not following the timer) and sometimes in a different order(you can test this by looking at the print out)
var randomTimeInteger = Int()
var stopMusicTimer = NSTimer()
var startMusicTimer = NSTimer()
override func viewDidLoad(){
playMusic()
}
//Call to get random interger
func generateRandomInteger(){
print("Inside generate random integer")
let lower = 3
let upper = 12
randomTimeInteger = lower + Int(arc4random_uniform(UInt32(upper - lower + 1)))
print("Random Integer is : \(randomTimeInteger)")
initiateTimer()
}
//Start the timer
func initiateTimer() {
print("Inside initate Timer")
//Set Timer
stopMusicTimer = NSTimer.scheduledTimerWithTimeInterval(Double(randomTimeInteger), target: self, selector: "stopMusic", userInfo: nil, repeats: true)
}
//Stop the music
func stopMusic(){
print("Inside stop music")
GameViewController.backgroundAudio?.stop()
startMusicTimer = NSTimer.scheduledTimerWithTimeInterval(3.0, target: self, selector: "playMusic", userInfo: nil, repeats: true)
}
func playMusic(){
print("Inside play music")
GameViewController.backgroundAudio?.play()
generateRandomInteger()
}
Any idea what the problem is? Or if there is a better way to do so? PLEASE HELP!
You have one timer that tries to stop the music every 3 to 12 seconds. And sometimes you create a timer that tries to start the music every 3 seconds. And sometimes you create more of these timers. So eventually you have lots and lots of timers that try starting and stopping the music at random time.
To stop a repeating timer, call invalidate.
And don't initialise the timers as you do, NSTimer() returns nothing useful. Just declare the variables as NSTimer?
There are really numerous viable approaches.
Starting from the top, I would sketch the design as follows:
func playMusic(completion: (ErrorType?)->()) {
play(randomDuration()) { error in
if error != nil {
completion(error)
return
}
delay(1.0, f: play)
}
}
func randomDuration() -> Double {...}
func play(duration: Double, completion: (ErrorType?)->()) {
let player = Player(...)
player.resume()
delay(duration) {
player.suspend()
completion(nil)
}
}
Function delay(_:f:) is implemented in terms of dispatch_after.
You will probably notice, that playMusic runs indefinitely. This is by your requirements, but in practice you need a way to stop it.