Does background CMDeviceMotion delivery depend on an active Core Location session?

I'm working on an iPhone app that continuously monitors device tilt with Core Motion. When the device has been held tilted forward past a threshold angle for a sustained period, the app raises a local notification.

The detection has to keep running while my app is in the background. The situation I need to detect is, by definition, one where the user is looking at some other app — if my app were in the foreground, there would be nothing to detect. So a foreground-only implementation would not implement the feature at all.

While testing this I ran into a behavior I would like to understand properly before I rely on it.

What I observe

CMMotionManager device-motion updates to a backgrounded app stop within a few seconds of the app leaving the foreground — unless a Core Location session is running at the same time. With location updates started under When In Use authorization and allowsBackgroundLocationUpdates = true, the device-motion callbacks continue for the whole time the app is backgrounded. Stop the location session, and they stop again.

I built a focused sample to measure this. It starts device-motion updates at 10 Hz on a background OperationQueue and counts every callback, records the count on didEnterBackground, and on willEnterForeground logs how many arrived during the interval against how many would be expected at 10 Hz. Measured on an iPhone running iOS 26.5.2, launched from the Home screen with no debugger attached:

location ON  | background 137s | received 1366 / expected ~1373   (99.5%)
location OFF | background 129s | received    2 / expected ~1286   (0.16%)

Both callbacks in the second run arrived immediately after the transition to the background; nothing arrived over the remaining two minutes.

One thing that cost me a test cycle, in case it saves someone else one: the difference only shows up when the app is launched from the Home screen. With the Xcode debugger attached the app is not suspended, and both cases deliver callbacks for the entire interval.

The location session in the sample is configured as low as I can make it, since the app never reads the coordinates:

manager.desiredAccuracy = kCLLocationAccuracyThreeKilometers
manager.distanceFilter = 3000
manager.activityType = .other
manager.pausesLocationUpdatesAutomatically = false
manager.requestWhenInUseAuthorization()

// started from the foreground, once authorization is granted
manager.allowsBackgroundLocationUpdates = true
manager.startUpdatingLocation()

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    // Intentionally empty. This sample does not use the location values.
}

My questions

  1. Is continuous CMDeviceMotion delivery to a backgrounded app dependent on an active Core Location session? Is that intended and expected behavior on current iOS versions, or an implementation detail I should not be relying on?

  2. If it is expected behavior, what configuration would you recommend for an app in this situation? Specifically, is kCLLocationAccuracyThreeKilometers with a large distanceFilter sufficient to sustain the session, or does reliable delivery require a higher accuracy or a smaller distance filter?

  3. Is there another supported API or background execution mechanism that delivers continuous device-motion or accelerometer data to a backgrounded app? I am aware of CMSensorRecorder for retrospective retrieval, but I need to react in near real time. I would like to be sure I am not overlooking a more appropriate API.

Environment: iOS 18.0 and later, iPhone only, Swift / SwiftUI. I have the focused sample project available if it would be useful.

Thanks very much for any help.

So, let me start with what's happening here:

CMMotionManager device-motion updates to a backgrounded app stop within a few seconds of the app leaving the foreground.

iOS's background execution model is built around the idea that the "normal" state for a backgrounded app is "suspended". That means it exists in memory but is not given any execution time by the scheduler, so it can't actually "do" anything. This minimizes power consumption and overall system load, and will also allow the system to quickly get the app "working" again as needed.

Exactly what/when the system runs apps in the background is a very complicated topic. Our "core" background services are described in "Configuring background execution modes“; however, there are a very large number of other APIs that will also wake the app (typically for very short intervals), so that document should not be considered the complete list.

One critical point here— which background categories your app can use is determined by the functionality you provide to the user, NOT what would be most helpful/useful to your app’s implementation. As the most straightforward example, apps that "let people call other people" are allowed to use “VoIP". Other kinds of apps may NOT use “VoIP", even if it would otherwise be very useful.

That leads to here:

  1. Is continuous CMDeviceMotion delivery to a backgrounded app dependent on an active Core Location session?

Yes and no. In purely technical terms, there isn't any direct requirement that you use CoreLocation, as what matters is that your app is "awake in the background", not exactly what API is keeping it awake. Case in point, your app would work exactly the same if you were using "audio" instead of "location". However, in practice, most background categories have more involved activation requirements and/or don't really "fit" with the use case of motion analysis apps, which is why many motion tracking apps end up using "location".

Is that intended and expected behavior on current iOS versions, or an implementation detail I should not be relying on?

Yes, the system is designed to work this way.

  1. If it is expected behavior, what configuration would you recommend for an app in this situation? Specifically, is kCLLocationAccuracyThreeKilometers with a large distanceFilter sufficient to sustain the session, or does reliable delivery require a higher accuracy or a smaller distance filter?

That's not a question I can answer for you. The general answer here is that you should use the lowest accuracy configuration that meets your needs. More importantly, if your app doesn't involve tracking the user’s location, then you shouldn't be using "location".

  1. Is there another supported API or background execution mechanism that delivers continuous device-motion or accelerometer data to a backgrounded app? I am aware of CMSensorRecorder for retrospective retrieval, but I need to react in near real time. I would like to be sure I am not overlooking a more appropriate API.

One small side comment here. I'm a HUGE fan of CMSensorRecorder, even for "real time" analysis. The issue here is that, by FAR the most common issue I see in motion analysis apps is that delivery/processing issues in the app are causing motion events to be delayed/dropped, typically without the app even realizing it's happening.

Putting that in more concrete terms, most apps treat the motion event they just received as happening "now" and that the stream of events they're receiving is evenly spaced in time. However, it’s also common for other app activity to delay event delivery and, in the worst case, even cause events to be dropped. Now, this can be accounted for by looking at the even timestamp, however, that still leaves the app with a complicated design trade-off to negotiate. Namely:

  1. If they block the thread to long analyzing the data, then they risk blocking event delivery, delaying or losing motion events.

  2. If they immediately shift every event they receive to a different thread, then you end up generating a high volume of thread wake-up "noise", which is also quite wasteful.

The most direct solution to that would be to have your event delivery thread collect events together as they arrive, then periodically send them to another thread for actual processing...

...which then leads back to CMSensorRecorder. You can get EXACTLY the same result with CMSensorRecorder by simply polling CMSensorRecorder.accelerometerData(from:to:) at a short, fixed interval. However, you're now guaranteed that, regardless of your app’s implementation:

  • All samples will be at a consistent cadence without any missed samples.

  • If anything goes "wrong", your app will still have access to the samples.

Finally, one question here:

I need to react in near real time

Why? Tying this back to the issue of background execution, if you're not collecting high-accuracy location, then why do you ACTUALLY need the data in "real time"?

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Thank you — this is exactly the kind of answer I was hoping for, and the point about background categories following user-facing functionality rather than implementation convenience is taken.

Let me answer your question directly.

Why near real time

The app's entire user-facing output is a single local notification whose meaning is "this is happening right now." It is an interruption, not a record. Once the device has been held past the threshold angle continuously for a short hold period, the notification has to arrive while that is still true. A notification that arrives afterwards describes a moment the user can no longer act on, which removes the reason for the feature to exist. Concretely, my latency budget is seconds to tens of seconds, not minutes.

With that said, I want to separate two parts of your suggestion, because I think you are right about one and I am not sure about the other.

Sampling. I take your point completely. The failure mode you describe — events silently delayed or dropped while the app treats the stream as evenly spaced — is a real weakness in what I have now, and I had not been checking timestamp the way I should. Polling CMSensorRecorder.accelerometerData(from:to:) on a fixed interval is clearly the more robust way to obtain the data, and I would like to move to it regardless of how the rest of this resolves.

Evaluation and notification. This is where I am stuck. Polling still requires my process to be executing. If the app is suspended, nothing polls, nothing evaluates the samples against the threshold, and no notification is scheduled. The recorder faithfully accumulates data that no one reads until the user next opens the app — at which point the notification would be describing something that ended long ago. So as far as I can tell, CMSensorRecorder fixes the quality and completeness of the data, but not the requirement that my code be running at a tens-of-seconds cadence.

So what I would most like to ask:

  1. Am I wrong about the above? Is there a wake-up path associated with CMSensorRecorder, or anywhere else, that gets my code running periodically without a background mode?

  2. BGAppRefreshTask is the periodic-background-work mechanism I am aware of, but my understanding is that it is opportunistic and typically scheduled on the order of tens of minutes or longer, which is well outside the budget above. Is that understanding accurate?

  3. Following your point that background categories are determined by the functionality provided to the user, it may help if I describe that functionality concretely. The app is a posture-awareness tool. It watches the angle at which the user is holding their iPhone, and when the device has been held tilted forward past a threshold for a sustained period — the head-down position people settle into while reading on a phone — it raises a local notification so the user can correct it. That is the whole of it. And to be clear on your point, it never reads, stores, or transmits the user's location.

    Given that, is there a supported background execution mechanism I should be using instead? Or is it simply that iOS does not provide continuous background execution for this class of app, and I should be designing around a foreground-only or opportunistic model?

I would rather build what the platform actually supports than lean on something I should not be leaning on. A plain answer to (3), including "no," is the most useful thing you could give me.

Thank you again for the detailed reply — the CMSensorRecorder point in particular is something I would not have arrived at on my own.

So, organizing the issues here a bit more concretely, there are two distinct issues at work here:

(1) How should I collect the motion data I want to analyze?

You were using CMDeviceMotion and I'm recommending CMSensorRecorder, for the reasons I've previously described. The one comment I'll add here is on this issue:

Concretely, my latency budget is seconds to tens of seconds, not minutes.

That kind of time budget is very typical of "user motion" use cases, where you're really trying to make an inference about some kind of "larger" real-world motion, NOT simply looking at how the device itself is actually moving. Things like step counting, exercise tracking, or large-scale device movement.

CMDeviceMotion becomes more useful if/when you're actually trying to track EXACTLY how ITSELF is moving, particularly in real time— things like motion-based game controls or augmented reality. Note that those kinds of use cases are also why the CMDeviceMotion.deviceMotion property exists. It’s there so things like render engines can simply retrieve the "latest" position each time it renders a frame, instead of trying to receive positions and then transferring them into the render engine.

One final comment on this issue, mostly for future readers. People looking at CMSensorRecorder tend to think of it as a "historical analysis" API because background data collection is one of its more obvious features. However, that's NOT actually why I recommend looking at it "first". Its most immediate benefit is that it gives you high-quality motion data while sidestepping all of the issues our real-time APIs create. Access to historical data is just an additional benefit.

(2) How can my app stay awake in the background so that it can process data?

Both CMSensorRecorder and CMDeviceMotion both work "in the background" (meaning, they'll return data if your app is awake), but CoreMotion itself doesn't provide any direct support for actually running in the background. Motion apps that run in the background use one or more of our other background APIs to stay awake— typically some combination of CoreLocation, HKWorkoutSession, and (possibly) "audio". Critically, which of those categories an app can use is determined by the functionality provided to the user, not the app’s broader technical requirements. That is, a run tracking app may use "location" because it does things like provide real-time directions on the user’s route or tracks and then shows the user their exact path they ran, not because it's also useful for collecting motion data.

Shifting to your specific questions for completeness:

  1. Am I wrong about the above? Is there a wake-up path associated with CMSensorRecorder, or anywhere else, that gets my code running periodically without a background mode?

No. The only thing I'll add is that this is true of all CoreMotion APIs (CMDeviceMotion, etc.) and not specific to CMSensorRecorder.

  1. BGAppRefreshTask is the periodic-background-work mechanism I am aware of, but my understanding is that it is opportunistic and typically scheduled on the order of tens of minutes or longer, which is well outside the budget above. Is that understanding accurate?

Yes, that's correct. However, the other issue here is that its scheduling is HEAVILY driven by actual app usage patterns, which means it may not work very well for use cases like this:

The app's entire user-facing output is a single local notification whose meaning is "this is happening right now."

...where the user doesn't really "use" the app, since they don't actually spend very much (if any) time actually interacting with the device. It might still be worth considering as an option, but you need to be aware that it has limitations. Two other points I'd make here:

  1. This forum post has a good overview of the different task types and their expected behavior.

  2. If you want to test or experiment with it, you need to be aware that the usage patterns on development hardware can DRAMATICALLY distort the APIs’ behavior, giving a very distorted view of how the API will behave in real-world use. See this forum post for the full details.

  1. ...Given that, is there a supported background execution mechanism I should be using instead? Or is it simply that iOS does not provide continuous background execution for this class of app?

There definitely isn't one that directly satisfies what you've specifically described.

...and I should be designing around a foreground-only or opportunistic model?

Ultimately, that's an app design issue I can't really answer. As I said, there isn't an API path that directly meets your needs, but it’s possible you could expand your product’s role/design in a way that would allow one of those background categories to be used.

A plain answer to (3), including "no," is the most useful thing you could give me.

Unless you significantly alter your app’s overall design/concept/feature set, you'll need to design around a foreground-only/opportunistic model.

Finally, I do have one "trick" that could be helpful with the opportunistic approach. One of the issues many developers run into here is recovering from failures/disruptions. For example, the combination of BGAppRefreshTask and/or silent push can let an app run for a small amount of time several times per hour, but if/when those ever "stop", there isn't any way the user would know that has happened. In the worst case, that can mean your app isn't actually "working", even though the user WOULD like your app to be "doing" its work.

The trick here is to use a local notification to create a sort of "watchdog" that will inform the user if/when something goes wrong. You do that by:

  1. Schedule one or more notifications to fire at some point "in the future" that's past the point you next hope/expect your app to have run. Those notifications basically say "My app needs to be opened" (or whatever else you want to say).

  2. Every time your app wakes up or runs, cancel the notifications you scheduled in #1 and schedule new notifications to fire "further in the future".

As long as your app is regularly running, the notification(s) will never actually fire as #2 above keeps pushing the notification further into the future. However, if/when anything goes wrong, then the notification will eventually fire, warning the user that there is a problem. Keep in mind that you can schedule multiple notifications, so you can do things like warn the user every 10m if they happened to miss/ignore/cancel your "first" warning notification.

Thank you again for the detailed reply — the CMSensorRecorder point in particular is something I would not have arrived at on my own.

You're very welcome, I'm glad that was helpful.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

That is exactly the plain answer I was hoping for, and I appreciate you giving it directly rather than leaving it ambiguous. Knowing that there isn't a path is far more useful to me than continuing to look for one.

Three things from your reply that were particularly valuable:

Separating the two issues. I had them tangled together. Once they are apart it is obvious that CMSensorRecorder and background execution are unrelated problems, and that solving the first does nothing for the second. I will be moving the sampling side over to CMSensorRecorder regardless of how I resolve the rest.

The point about BGAppRefreshTask scheduling being driven by actual app usage. I had understood the timing limits but not that the scheduling would be biased against exactly the kind of app that never gets opened. That would have been an unpleasant thing to discover after shipping.

The watchdog notification pattern. That is a genuinely clever inversion and I had not seen it described anywhere. Scheduling the failure message in advance and pushing it forward on every successful run, so silence is what triggers it, is the kind of thing that is obvious only after someone shows it to you. Thank you for writing it out.

I am going to step back and rethink the design around what the platform actually supports rather than trying to work around it. Thanks again for two very thorough replies.

Does background CMDeviceMotion delivery depend on an active Core Location session?
 
 
Q