DispatchQueue.main.asyncAfter 与 on/off 开关

DispatchQueue.main.asyncAfter with on/off switch

我创建了以下结构,作为在网络连接速度较慢时提醒用户的一种方式。

当一个函数要调用服务器时,它会创建一个 ResponseTimer。这会设置延迟通知,仅当 responseTimer var isOn = true 时才会触发。当我的函数从服务器获得响应时,设置 responseTimer.isOn = false。

结构如下:

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

下面是我将如何使用它

func getSomethingFromFirebase() {

    var responseTimer = ResponseTimer()

    ref.observeSingleEvent(of: .value, with: { snapshot in
        responseTimer.isOn = false

        //do other stuff
    })
}

即使在 8 秒延迟完成之前响应返回,通知仍会触发。我在这里做错了什么???有没有更好的模式可以用于这样的事情?

感谢您的帮助!

更好的方法是使用DispatchSourceTimer可以取消

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

有几种方法:

  1. invalidate一个Timerdeinit

    其实现可能如下所示:

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

    然后你可以做:

    var responseTimer: ResponseTimer?
    
    func getSomethingFromFirebase() {
        responseTimer = ResponseTimer()
        responseTimer.schedule()
    
        ref.observeSingleEvent(of: .value) { snapshot in
            responseTimer?.invalidate()
    
            //do other stuff
        }
    }
    
  2. asyncAfterDispatchWorkItem 结合使用,您可以 cancel:

    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()
        }
    }
    
  3. 使用DispatchTimerSource,超出范围时自动取消:

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

在所有三种模式中,当 ResponseTimer 超出范围时,计时器将被取消。