Search code examples
swifttimergrand-central-dispatchbackground-process

Cancel scheduled event for background


So i have this function that should upload some data of a recording every 15 minutes to backend. This should happen even if the user put the app in background. And therefor the Timer.schedule doesn't work. Hence i figured out i need to put this on a dispatchQueue code seen below:

    while Date(timeIntervalSinceNow: 86400).compare(Date(timeIntervalSinceNow: incrementer)) == .orderedDescending {
            incrementer += 900
            DispatchQueue.main.asyncAfter(deadline: .now() + incrementer, execute: {
                    DispatchQueue.main.async {
                        self.viewModel.upload(data: self.audioRecorder.url, filePath: self.audioRecorder.url)
                        self.audioRecorder.stop()
                        self.fileIncrementer += 1
                        self.startRecording()
                    }
            })
    }

This how ever cause a problem when i cancel the task in advance by pressing stop button and presenting the next viewController this dispatchQueue just keep processing.

What can be done is to throw in:

    if var topController = UIApplication.shared.keyWindowInConnectedScenes?.rootViewController {
                        while let presentedViewController = topController.presentedViewController {
                            topController = presentedViewController
                        }
                        if (topController as? UINavigationController) != self.navigationController {
                            print("cought")
                            return
                        }
    }

Which would stop it if i were in another VC, but if i then enter a new session, i will have double events. So how do i do this in a nice way?


Solution

  • ... when ... pressing stop button ... this is dispatchQueue just keep processing

    Yep, this is precisely why you shouldn’t use this multiple asyncAfter pattern. (Plus, you may eventually run into timer coalescing problems.) The Timer is the right way to do this.

    So i have this function that should upload some data of a recording every 15 minutes to backend. This should happen even if the user put the app in background.

    This is the problem. Neither Timer nor asyncAfter can solve this problem.

    I infer that you’ve concluded that you can accomplish this with asyncAfter, but you can’t. Perhaps you were testing this process while running the app attached to the debugger, which artificially keeps the app running in the background. But try it on a device, not attached to the debugger, and you’ll find that the app does not continue to run when the user leaves the app. This asyncAfter technique will not accomplish what you want.

    The exception here is if your app has asked for one of the background capabilities (e.g. VOIP, music, navigation, etc.). But if the app is destined for the App Store, Apple is quite diligent in rejecting apps that request background services for unapproved purposes. This sort of constant background operation kills batteries in the matter of hours, so Apple is looking after the best interests of its user base.

    So, bottom line, if your app has a background execution capability, a Timer is the way to do this periodic process. And if the app doesn’t have a background execution capability, neither Timer nor any GCD calls will do the job.