7 Replies
      Latest reply on Jun 19, 2019 1:15 AM by michaelr
      prabufromtorrance Level 1 Level 1 (0 points)

        Hi there

         

        I've read many posts about running tasks in the background, however I have not been able to get an answer to our bluetooth entitled app's problem.

         

        We have an app that connects to a bluetooth device(Beacon) and sends and receives BLE data in the background. There is no problem when the app is in foreground, the problem we face is when the app goes background and the device is locked. Yet it is extremely important that our app is sending data through web service every 5 minutes till the user exits the region

         

        We start a task with beginBackgroundTask, set a timer to executes after 3 minutes. But when the user enters the region, beacon is detected and if the app is in the background, only one request is sent after 3 minutes and it doesn't repeat again till the user exits the region.

         

        func doBackgroundTask() {

                DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async {

                    self.beginBackgroundTask()

                    self.countdownTimer = Timer.scheduledTimer(timeInterval: 180, target: self, selector: #selector(self.requestService), userInfo: nil, repeats: true)

                    RunLoop.current.add(self.countdownTimer, forMode: .common)

                    RunLoop.current.run()

                }

            }

         

            func beginBackgroundTask() {

                self.backgroundTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {

                    // you can't call endBackgroundTask here and you don't need to

                })

                assert(self.backgroundTask != UIBackgroundTaskIdentifier.invalid)

            }

         

            func endBackgroundTask() {

                if self.backgroundTask != nil {

                    UIApplication.shared.endBackgroundTask(self.backgroundTask)

                    self.backgroundTask = UIBackgroundTaskIdentifier.invalid

                }

            }

         

        How do I make the app run in the background for once in every 5 mins till the user exits the region.

        • Re: How to run timer in background for unlimited time while iphone screen locked or unlocked and calling web service
          eskimo Apple Staff Apple Staff (11,825 points)

          How do I make the app run in the background for once in every 5 mins till the user exits the region.

          There’s no direct way to do that.  To start, I recommend you read my UIApplication Background Task Notes post, which explains a lot of the background (hey hey!) to this issue.

          We start a task with beginBackgroundTask, set a timer to executes after 3 minutes. But when the user enters the region, beacon is detected and if the app is in the background, only one request is sent after 3 minutes and it doesn't repeat again till the user exits the region.

          The problem here is that background tasks started while you’re in the background have a much shorter time limit.  While the exact limit is not documented — it has changed in the past and it may well change again in the future — the current value is about 30 seconds.

          So here’s what happens:

          1. The system resumes (or relaunches) your app in the background because the user enters your region.

          2. You schedule a timer for 3 minutes and start a background task to prevent the app from being suspended.

          3. After 30 seconds the background task expires.  At this point one of two things happens:

            • If you have an expiry handler, that will be called.  It must end the background task, at which point your app will suspend (A).

            • If you have no expiry handler, or it fails to end the background task promptly, the watchdog will kill your app (B).

          4. This means that the timer will not run after 3 minutes.  Either your app is suspended (which will prevent the timer from running) or your app is terminated.

          5. This state continues until the user leaves your region.  At that point one of two things happens:

            • In case A, the system will resume your app in the background.  Your timer is long overdue, so it’ll fire now.

            • In case B, the system will relaunch your app in the background.  As this is a new process, the timer no longer exists.

          IMPORTANT If you want to test this, make sure to run your app from the Home screen rather than from Xcode.  The Xcode debugger disables the watchdog, prevents your app from suspending, and so on.  You can read more advice on this topic, albeit in a different context, in my Testing Background Session Code post.


          Oh, btw, I wanted to address a few of your code snippets.  To start, this is a concern:

          DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async {
              self.beginBackgroundTask()
              self.countdownTimer = Timer.scheduledTimer(timeInterval: 180, target: self, selector: #selector(self.requestService), userInfo: nil, repeats: true)
              RunLoop.current.add(self.countdownTimer, forMode: .common)
              RunLoop.current.run()
          }

          Here you’re consuming a dispatch worker thread in order to run a timer.  That’s very wasteful.  Dispatch has a limited number of worker threads available to it, and you shouldn’t waste one like this.

          If necessary you can use either:

          • Use the Thread API to start your own thread for this (see my Swift Forums post on this topic)

          • Use a Dispatch timer source, which target a queue

          In this case, however, neither of these is a great idea.  Consider the thread safety of your backgroundTask property.  While it’s possible to start and end background tasks from any thread, the background task expiry handler will be called on the main thread.  So, if the expiry handler touches the backgroundTask property, every reference to that property should be in the main thread [1].

          The simplest way to achieve that is to schedule your timer on the main thread.  If necessary, it can then dispatch work off to a secondary thread/queue.


          And this one is also worrying:

          self.backgroundTask = UIApplication.shared.beginBackgroundTask(expirationHandler: {
              // you can't call endBackgroundTask here and you don't need to
          })

          This is wrong.  You must start every background task you end, even if the expiry handler is called.  Failing to do this will get your app killed by the watchdog.

          I’m not sure what caused you to say that “you can't call endBackgroundTask here”, but if you post some details about the problem you saw I should be able to explain what went wrong.

          Share and Enjoy

          Quinn “The Eskimo!”
          Apple Developer Relations, Developer Technical Support, Core OS/Hardware
          let myEmail = "eskimo" + "1" + "@apple.com"

          [1] The alternative is to confine every reference to the property to some other thread/queue, but then your expiry handler will need to call that thread/queue synchronously because your expiry handler must guarantee that the background task is ended before it returns.

          • Re: How to run timer in background for unlimited time while iphone screen locked or unlocked and calling web service
            ManuelMB Level 3 Level 3 (175 points)

            Maybe you could prevent the  app goes background and the device is locked.

             

            UIApplication.shared.isIdleTimerDisabled = true