DispatchQueue.main.asyncAfter is inaccurate
asyncAfter
is only guaranteed to wait at least as long as specified.
You probably should still be using Timer if you want exact time intervals on iOS9.
Timer.scheduledTimer(timeInterval: delay,
target: self,
selector: #selector(executeClosure),
userInfo: nil,
repeats: false)
With executeClosure being a function that executes the last saved closure.
DispatchQueue.asyncAfter doesn't work the way I thought it would?
The other answers listed here are effective, but they aren't quite as robust as they could be. Calling task.resume()
at fixed time intervals does not guarantee that requests are actually sent at those intervals. You are at the mercy of the internal queue maintained by URLSession.shared
. Adding tasks at 4 second intervals does not mean they will necessarily be sent in that time. It also makes no guarantees about how long a request will take (think mobile networks with poor service). As for Data(contentsOf:)
, it provides absolutely no real features or customization, such as error handling.
A more robust solution would be to use a DispatchGroup
and to only initiate a new request 4 seconds after the previous one has completed.
class ViewController
let serialQueue = DispatchQueue(label: "networkRequests")
let networkGroup = DispatchGroup()
func myFirstFunc {
for item in items {
self.mySecondFunc(item: item, completionHandler: {(completionItem) in
// you shouldn't need this
})
}
}
func mySecondFunc(item: someType, completionHandler: @escaping (String?) -> Void) {
let task = session.dataTask(with: request, completionHandler: {(data, response, error) in
// stuff
completionHandler(changedItem)
Thread.sleep(forTimeInterval: 4) // Wait for 4 seconds
networkGroup.leave() // Then leave this block so the next one can run
})
self.networkGroup.notify(queue: serialQueue) {
networkGroup.wait() // Wait for the previous block to finish
networkGroup.enter() // Enter a new block
task.resume()
print(Date())
}
}
}
This will guarantee that each subsequent request is sent no sooner than 4 seconds after the previous one finishes, and does not rely on outside factors (like URLSession
's internal queue or network stability) to maintain proper timing, without sacrificing the modern features of URLSession
s.
Bug possibly related to DispatchQueue.main.asyncAfter in UIButton action when button is spammed/rapidly and repeatedly pressed
Here is possible solution - just don't give possibility for a user to generate not desired taps
sender.isEnabled = false // don't allow action until question updated
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
self.questionLabel.text = self.quizArray[self.questionNumber].text
sender.isEnabled = true // << allow user interaction
}
in second do the same
why 'asyncAfter' is not act in exact time?
It's right there in the method name-- "asyncAfter". The method isn't guaranteed to run code at a specific time, it runs code after a specific time. That might be immediately after the time or it might be later on. You should think of the method as meaning "wait at least this long", not "run code at exactly this time".
If you need more precise timing, consider using Timer
or else creating a DispatchSourceTimer
.
Mutating conditional in DispatchQueue.main.asyncAfter behavior
Will the DispatchQueue always wait until all previous code is done executing, or is there any scenario in which the completion handler can run "in between" some other block of codes execution?
Depends if it's a Sequential queue or Concurrent queue. Sequential queue will finish task1 before starting task2; concurrent may run them in parallel. main
thread is sequential, so you are good there, but...
You said yourself "sometime during these 3.5 seconds a function is called that sets viewModel.someBool to false". What if that "some time" is 1 nanosecond after a delayed task was picked up and started running?.. So now your function that changes viewModel.someBool to false needs to wait for your delayed task to complete.
So either this should be OK for your code (which is preferable, since such a strong dependency on order, especially in UI, usually means some design issues), or you need to guarantee the order in your code
DispatchQueue.main.asyncAfter with on/off switch
There are a few approaches:
invalidate
aTimer
indeinit
Its implementation might look like:
class ResponseTimer {
private weak var timer: Timer?
func schedule() {
timer = Timer.scheduledTimer(withTimeInterval: 8, repeats: false) { _ in // if you reference `self` in this block, make sure to include `[weak self]` capture list, too
// do something
}
}
func invalidate() {
timer?.invalidate()
}
// you might want to make sure you `invalidate` this when it’s deallocated just in
// case you accidentally had path of execution that failed to call `invalidate`.
deinit {
invalidate()
}
}And then you can do:
var responseTimer: ResponseTimer?
func getSomethingFromFirebase() {
responseTimer = ResponseTimer()
responseTimer.schedule()
ref.observeSingleEvent(of: .value) { snapshot in
responseTimer?.invalidate()
//do other stuff
}
}Use
asyncAfter
withDispatchWorkItem
, which you cancancel
:class ResponseTimer {
private var item: DispatchWorkItem?
func schedule() {
item = DispatchWorkItem { // [weak self] in // if you reference `self` in this block, uncomment this `[weak self]` capture list, too
// do something
}
DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: item!)
}
func invalidate() {
item?.cancel()
item = nil
}
deinit {
invalidate()
}
}Use
DispatchTimerSource
, which cancels automatically when it falls out of scope:struct ResponseTimer {
private var timer: DispatchSourceTimer?
mutating func schedule() {
timer = DispatchSource.makeTimerSource(queue: .main)
timer?.setEventHandler { // [weak self] in // if you reference `self` in the closure, uncomment this
NotificationCenter.default.post(name: notification, object: nil)
}
timer?.schedule(deadline: .now() + 8)
timer?.activate()
}
mutating func invalidate() {
timer = nil
}
}
In all three patterns, the timer will be canceled when ResponseTimer
falls out of scope.
DispatchQueue.main.asyncAfter repeatedly called in iOS 14.0 Beta
In
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
self.show.toggle() // << this !!
}
}
.. self.show.toggle()
makes body
rebuild and NavigationLink
recreated (as it depends on show
, and thus .onAppear
called again (as it is new link), and you see - go by cycle.
So nothing wrong with DispatchQueue.main.asyncAfter
.
Probably you meant to attach that .onAppear modifier to root view, ie. probably NavigationView
.
Update: below tested as worked on Xcode 12 / iOS 14
struct ContentView: View {
@State var show = false
var body: some View {
NavigationView{
VStack{
NavigationLink(destination: Text("New View"), isActive: $show, label: {
Image("main_logo").renderingMode(.original).frame(width: 100, height: 100)
})
} .navigationBarHidden(true)
.navigationBarTitle(Text("Home"))
.edgesIgnoringSafeArea([.top, .bottom])
}.preferredColorScheme(.dark) // white tint on status bar
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
self.show.toggle()
}
}
}
}
Why putting a DispatchQueue delay function inside another DispatchQueue has no effect in Swift
There are a number of ways you could do this. Here are 3:
Use a timer to repeat a block every second
var i = 0
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
i += 1
print("hello \(i)")
if i == 5 {
timer.invalidate()
}
}
Dispatch multiple async tasks
for i in 1...5 {
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(i)) {
print("hello \(i)")
}
}
Note: This queues all of the tasks up front and could overflow a queue if there were a large number of them.
Run loop in background with sleep and switch to foreground to print
DispatchQueue.global().async {
for i in 1...5 {
sleep(1)
DispatchQueue.main.async {
print("hello \(i)")
}
}
}
Note: This will drift a little (not be 1 second apart) if the work in the loop takes a significant amount of time.
Related Topics
Generate an Rsa Public/Private Key Pair
Xcode Incorrectly Reporting Swift Access Race Condition
Animate the Fractioncomplete of Uiviewpropertyanimator for Blurring the Background
How to Cast an _Nsmallocblock_ to Its Underlying Type in Swift 3
How to Open Safari Extension Toolbaritem Popover Programmatically
How to Integrate Latest Sdwebimage API in My Swift Based Project
How to Install the Alamofire 4.0 in Xcode 8.0
How to Convert Any to Int in Swift
How to Use Swift Package Manager with an Existing MACos Project
How to Get Row Index in Swiftui List
Error: Unable to Spawn Process (Argument List Too Long) in Xcode Build
How to Override Layout of Nstableheaderview
Wait Until an Asynchronous API Call Is Completed - Swift/Ios