'DispatchQueue.main.asyncAfter with on/off switch
I whipped up the below struct as a way to alert the user when there's a slow network connection.
When a function is going to make a call to the server, it creates a ResponseTimer. This sets a delayed notification, which only fires if the responseTimer var isOn = true. When my function get's a response back from the server, set responseTimer.isOn = false.
Here's the struct:
struct ResponseTimer {
var isOn: Bool
init() {
self.isOn = true
self.setDelayedAlert()
}
func setDelayedAlert() {
let timer = DispatchTime.now() + 8
DispatchQueue.main.asyncAfter(deadline: timer) {
if self.isOn {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: toastErrorNotificationKey), object: self, userInfo: ["toastErrorCase" : ToastErrorCase.poorConnection])
}
}
}
And here's how I'd use it
func getSomethingFromFirebase() {
var responseTimer = ResponseTimer()
ref.observeSingleEvent(of: .value, with: { snapshot in
responseTimer.isOn = false
//do other stuff
})
}
Even in cases where the response comes back before the 8 second delay completes, the notification is still fired. What am I doing wrong here??? Is there a better pattern to use for something like this?
Thanks for the help!
Solution 1:[1]
There are a few approaches:
invalidateaTimerindeinitIts 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
asyncAfterwithDispatchWorkItem, 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.
Solution 2:[2]
A better way is to use DispatchSourceTimer which can be cancelled
var timer : DispatchSourceTimer?
func startTimer()
{
if timer == nil {
timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
timer!.schedule(deadline: .now() + .seconds(8))
timer!.setEventHandler {
DispatchQueue.main.async {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: toastErrorNotificationKey), object: self, userInfo: ["toastErrorCase" : ToastErrorCase.poorConnection])
}
self.timer = nil
}
timer!.resume()
}
}
func getSomethingFromFirebase() {
startTimer()
ref.observeSingleEvent(of: .value, with: { snapshot in
self.timer?.cancel()
self.timer = nil
//do other stuff
})
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | vadian |
