Using delay DispatchQueue in “for in loop”

别来无恙 提交于 2021-02-11 14:08:29

问题


The task is to change the background color once a second. Was used "for in loop". For delay, a DispatchQueue was used. Everything seems to be fine, but it was noticed that after 10 iterations, the background color begins to change with a delay of 2 seconds, a little later in 3 seconds. The more iterations, the greater the delay. I displayed time in the console (seconds) to see how it changes. I see the results, but I do not understand what is wrong. I did the task through a timer, there were no problems, but I want to understand what is wrong with the DispatchQueue delay.

for i in 1...150 {

    DispatchQueue.main.asyncAfter(deadline: .now() + Double(i)) {

        self.view.backgroundColor = UIColor(red: .random(in: 0...1),
                                          green: .random(in: 0...1), 
                                           blue: .random(in: 0...1), 
                                          alpha: 1)
         print("\(ymd)")

         ymd = self.myCalendar.dateComponents([.second], from: Date())
    }
}

Console:

second: 21 isLeapMonth: false 
second: 21 isLeapMonth: false 
second: 22 isLeapMonth: false 
second: 23 isLeapMonth: false 
second: 24 isLeapMonth: false 
second: 25 isLeapMonth: false 
second: 26 isLeapMonth: false 
second: 27 isLeapMonth: false 
second: 28 isLeapMonth: false 
second: 29 isLeapMonth: false 
second: 30 isLeapMonth: false 
second: 32 isLeapMonth: false 
second: 33 isLeapMonth: false 
second: 33 isLeapMonth: false 
second: 35 isLeapMonth: false 
second: 35 isLeapMonth: false 
second: 37 isLeapMonth: false 
second: 37 isLeapMonth: false 
second: 39 isLeapMonth: false 
second: 39 isLeapMonth: false 
second: 41 isLeapMonth: false 
second: 41 isLeapMonth: false 
second: 44 isLeapMonth: false 
second: 44 isLeapMonth: false 
second: 44 isLeapMonth: false 
second: 47 isLeapMonth: false 
second: 47 isLeapMonth: false 
second: 47 isLeapMonth: false 
second: 50 isLeapMonth: false 
second: 50 isLeapMonth: false 
second: 50 isLeapMonth: false 
second: 54 isLeapMonth: false 
second: 54 isLeapMonth: false 
second: 54 isLeapMonth: false 
second: 57 isLeapMonth: false 
second: 57 isLeapMonth: false 
second: 57 isLeapMonth: false 
second: 57 isLeapMonth: false 
second: 1 isLeapMonth: false 
second: 1 isLeapMonth: false 
second: 1 isLeapMonth: false 
second: 1 isLeapMonth: false 
second: 6 isLeapMonth: false 
second: 6 isLeapMonth: false 
second: 6 isLeapMonth: false 
second: 6 isLeapMonth: false 
second: 6 isLeapMonth: false 
second: 11 isLeapMonth: false 
second: 11 isLeapMonth: false 
second: 11 isLeapMonth: false 
second: 11 isLeapMonth: false 
second: 11 isLeapMonth: false 
second: 17 isLeapMonth: false 
second: 17 isLeapMonth: false 
second: 17 isLeapMonth: false 
second: 17 isLeapMonth: false 
second: 17 isLeapMonth: false 

回答1:


This is a result of “timer coalescing”, in which the “leeway” for the dispatched block is 10% of the delay, up to a max of one minute of leeway. (This is buried in the libdispatch code.) It’s a power saving feature to coalesce/group distant, independently scheduled tasks to run at the same time to avoid unnecessary spinning up the hardware too many times. The easiest way to avoid this coalescing is to use a repeating timer:

var counter = 0

Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in
    counter += 1

    guard let self = self, counter <= 150 else {
        timer.invalidate()
        return
    }

    self.view.backgroundColor = UIColor(red: .random(in: 0...1),
                                      green: .random(in: 0...1),
                                       blue: .random(in: 0...1),
                                      alpha: 1)

    let ymd = self.myCalendar.dateComponents([.second], from: Date())
    print(ymd)
}

Note the use of the [weak self] pattern, to avoid having the timer keep a persistent reference to the view controller. And with that guard statement, we’ll invalidate the timer if the view controller is dismissed.

BTW, if you want to make the color change less jarring, animate the change:

var counter = 0

Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in
    counter += 1
    guard let self = self, counter <= 150 else {
        timer.invalidate()
        return
    }

    UIView.animate(withDuration: 0.1) {
        self.view.backgroundColor = UIColor(red: .random(in: 0...1),
                                          green: .random(in: 0...1),
                                           blue: .random(in: 0...1),
                                          alpha: 1)
    }

    ...
}


来源:https://stackoverflow.com/questions/61147543/using-delay-dispatchqueue-in-for-in-loop

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!