Welcome to Under the Radar, a show about independent iOS app development. I'm Marco Arment. And I'm David Smith. Under the Radar is usually not longer than 30 minutes, so let's get started. So today I want to talk about power, or in the context of the ways we use it, battery life is probably more specific because we're not particularly...
That is the limiting factor for a great many of our apps and the way that they get used, especially for things that we both make, which are long-lived, long-run applications. So if someone might... use Pedometer++ to go out for an all-day hike, or they might open Overcast and listen to a podcast while going out on an all-day hike. And so in both of those cases, battery life and power consumption...
become an important aspect of the user experience. And battery life and power consumption are just a very difficult and challenging thing to optimize and to get right in a way that is sometimes... I personally find very frustrating, but it's something that I've been working on a lot recently. Like in the last few months, I've been working on a mode in Pedometer++ that's focused on sort of long endurance days. So like being able to go out and track a workout for maybe for 20 hours.
That's a very different thing than if you're going out for a 20, 30-minute run. And so we're doing a lot of work on battery life and power management. It seemed a good topic to discuss. I think I have some hints, some tips, some pitfalls, some frustrations. There's just a lot of things that kind of go into this because it's not as straightforward as... some of the other optimizations we might be doing where it's very easy to just open instruments.
You run your app with instruments connected and you can run the time profiler and you'll see where your app is spending the most time. And obviously that's related to power and battery consumption. But the power and battery implication of just using a lot of CPU is more nuanced and complicated. Because fundamentally, I think the thing that's challenging is ultimately power consumption is going to come from two things.
The one, the most obvious, is that it's just the work you're doing on the CPU, the GPU. Your app is doing something. That's what it's doing. And that work will use power. Just fundamentally, that's the nature of electronics and how these things work. And the other place you'll use power is if you are ever transmitting or receiving anything, whether that be sound.
You know, photons from the display, Wi-Fi, GPS, any of these kinds of things that are sending or receiving any kind of data, those things are using power. And fundamentally, improving and extending the battery life of your app and reducing its power consumption is fundamentally going to be about doing less of one of those two things. Like there is no magic other way to do this. It's just you have to either do less work.
And that will get one side of your power consumption down. Or you need to transmit or receive. you know, less, whether that be, you know, the way in which you are gathering, say, like GPS data. So that's very relevant for Domino's Plus Plus. I've done a lot of work on that. Or in terms of Pedometer Plus Plus, it's dealing with like sending and receiving of light from the heart rate sensor on the Apple Watch is fairly meaningful.
consumer of energy. And those ones particularly are challenging because they run into sort of physics limits. In the way that if someone's playing a podcast from a phone speaker... If it's oscillating air back and forth from the iPhone's speaker, that oscillation is taking energy. And there's a certain amount of – there's nothing you can do about that directly. And so in that case, you're kind of stuck that it's going to use a certain amount of –
sound energy to do it but there are things we can do on the consumption side and i think the process of finding a good fit for this is just it's a challenging road because inevitably in this your job you know your app has a job to be done And so you need to do that job. You have to see like the easiest way to use no battery is to do nothing.
That's not particularly helpful. So finding the line between actually doing something useful and not being overly zealous and, you know, using too much of the user's battery life and then them being frustrated, you know, because they wanted to listen to a podcast for 10 hours and it ran out of battery at nine.
Yeah, I mean the easiest way to have impressive battery life for your app is for people never to launch it, which is great. Problem solved. Yeah, most people who download your app eventually don't launch it anymore if they ever did. So this problem does solve itself. No, more seriously. And I think it's important to recognize, too, like there's a lot of different. patterns of usage of apps and... Sometimes – and there's also what people expect.
of you know apps and their power consumption those don't always necessarily line up and you have to kind of have reasonable expectations for what is realistic for your app in terms of power efficiency like If you have, I think, some kind of common utility app where it doesn't really do anything in the background, there is occasional need to launch it, interact with it for a relatively brief time, and then close it.
In that kind of scenario, you can do basically whatever you want, and it doesn't matter. Unless someone's going to be spending a very long time in your app. it's probably not going to be possible for it to do meaningful damage to the battery life, even if you're running all the radios and the GPS. There's only so much you can do in a minute or two.
background tasks especially, or if somebody is in constant interaction with the app. So for instance, if you have a navigation app, that's probably going to be on screen. for extended times. Certainly you're going to be probably using the GPS radio, and there's all sorts of, you know, in that kind of case, there's all sorts of little
little tricks you can do to reduce your power consumption. Like with GPS, for instance, you can reduce the precision that you need or the update frequency, core motion and core location. There's all these different parameters you can set. to make that more or less efficient. Basically, when you're looking at power efficiency, the major areas that matter a lot, are screen brightness, Radio and GPS usage.
which is a radio technically. So screen brightness, radio usage, and CPU usage. And GPU, of course. So processor usage. Those are the major areas that matter a lot. What surprised me about overcast development is that I found another area, as you mentioned a minute ago, that matters a lot, which is speaker usage. Like if somebody is using the built-in speaker to push that air to make that sound, that... surprised me when I learned that that actually was a pretty big power draw.
It was not possible to learn that through any kind of tooling. There is no instruments simulator or Xcode meter that tells me how much power the speaker is using. The way I learned that was a long time ago, I ran a test. I was curious to see what I was expecting to see was how efficient or inefficient AirPlay versus Bluetooth would be.
versus built-in headphones like that that was what i expected to find and what and what i expected to find was that using the wi-fi radio which is airplay was going to be less power efficient or more power hungry than using bluetooth And I expected both of those to be more power hungry than using the built-in headphone jack to power headphones directly. And what I found instead was, yeah, those matter.
If the phone is sitting around beaming stuff wirelessly to something else, that's actually pretty efficient for audio data. speaker in the phone by far. was the largest power drain for overcast. If you play out of the built-in speaker, it uses way more battery power than if you are sending it to headphones or speakers in any other way. So that's something that like there was no way for me to find that other than just like, all right, set up some tests, like, you know, make a test playlist.
Play it from the beginning and just have it just go for hours and hours and hours. I turned off sync, all that stuff, but tried to control it as best as I could. And it took a week or two to run these tests. But eventually, I got my results. And a lot of times, that's just what you have to do. Just like performance testing, a lot of times when you do performance testing, you're doing the CPU time thing with instruments or whatever. A lot of times things that you think will be really inefficient.
are actually fine, and you're spending a surprising amount of time where something is surprisingly costly that you wouldn't have guessed would be. And that exact same pattern holds, in my experience, for any kind of power and battery testing, too.
And there's all sorts of ways that you can unknowingly waste power. Like one of the common things is If you have some kind of internet connection requiring operation, say like a background sync, one of the common pitfalls for that that I have found is what happens if the sync fails? And this can happen, for instance, if the person is on a subway and they lose their connection for a few minutes or they're hiking in the mountains and they're offline. What happens if the person's offline?
and some operation in your app tries to use the connection. What can happen... is that can start going in a loop, just trying to hammer it over and over and over again, making that request over and over and over again, which can not only make the radios keep trying to connect, but can also burn CPU power by a lot. There's situations where you might not even realize, oh, my app seems really efficient.
But if you're offline, it turns out power usage spikes. And you might never know that in your testing unless you're doing offline power testing somehow, which is not a super common thing I don't think people test for. There's all sorts of surprises when you do power testing that you don't realize like, oh, if this weird condition happens, this will infinite loop or this process will spin forever or it'll keep trying to do this thing.
All sorts of things like that. But then you have to contend with... Even if you do a really good job of power optimization. If your app is inherently doing something that requires a lot of power that the user has explicitly ask you to do, whether that's playing podcasts very loudly out of the built-in speaker or just using an app constantly with a screen on that happens to use the network or GPS.
If that's what your app is doing, it's going to use a lot of power. And people are going to go into that battery section of their settings, and they're going to see your app on top. even if they were using it that whole time. So to some degree, this also creates marketing challenges or user support challenges in the sense that like...
Even if people like, you know, spend the energy knowingly with your app, they will still blame you if it's inefficient, even if there's nothing you can do about it. So there are a lot of sprawling places that power testing and power mindfulness can take you. Yeah, and I think in that, there's this funny aspect of...
Yeah, like there's a fundamental limit that you run into with power efficiency testing. Like at a certain point, this is at least has been my experience, is like I've done a bunch of things and I think there's some tools or strategies I've taken that I think would be useful to discuss. But broadly... It's like there's a certain, you will inevitably hit this point that this is just like, this is the floor and you can't go any lower.
And that is frustrating insofar as if that number is too high and you're going to either have to diminish the user experience in terms of do something different. Like that was ultimately, like in the case of Penometer++, what I found. is that if you run, you know, essentially continuous GPS and all of the workout tracking stuff that you can do on an Apple Watch,
It is, as far as I can tell, not possible to get the sort of the 20-hour goal that I was trying to get on my Apple Watch Ultra. It was just not possible. And the way I determined that is that I built... a stripped down version of my app that did nothing but those two things using just the system APIs as basic as I could make it and ran that. And like, so at this point, I'm confident that it's not.
you know, Swift UI getting into a funny state and using lots of data or other background processing or refresh, you know, things. It's like as minimal of an app as I possibly could. And I ran it. At that point, I determined this is the floor. If you do this and this and this, it is going to take at least this amount of battery to do it.
And so like in my case, I've worked out, well, what if I don't do this? What if I do this? I only get the heart rate every so often rather than every second, which is the default. And those kinds of operations are things that you just have to choose because otherwise that floor is fixed and there's nothing you can do about it.
In the sense of, in your case, it's like playing the sound at full volume out of the speaker. There is a floor there that is going to be ascribed to you that you can do nothing about. It doesn't mean there aren't things we can do to still be helpful. And I think that's...
The interesting aspect of a lot of this is it's finding the places where there are these pain points, and particularly it's finding them, yeah, finding the things that will get sort of run away from you, as I think something that very often seemed to be a pattern for what I was finding. email at work and like actually ran into a specific example of what you were just describing where
I had a thing that was loading, you know, loads map tiles. And typically when the app is set up such that you would download your map tiles ahead of time so that you have them sort of offline. But if you don't do that and then you go off into the wilderness, what I discovered is that as soon as you lose connectivity and it tries to load.
Every time SwiftUI updates the map, it will try load again and try load again and try load again. And obviously, like at some point, the system is intelligent about that in terms of if it, you know, if the. If it has no internet connection, I suspect those are failing instantaneously. So it's not like this terrible loop.
But it's still a bad loop and it's in this exact situation where the user does not want their battery to be running down. And so like that was something I have to resolve and have to deal with. Because you have to find these situations where you're going to use lots of battery. And I mean, the reality with this, that is, it feels very like scientific method, but it's this very interesting process you have to do of designing experiments.
to isolate particular variables, and then running actual real-world tests. Because there's something about... Battery testing that is frustrating is that to understand your battery consumption over eight hours, you have to do the thing for eight hours. And this is a situation where I very much envy Overcast being able to just play audio rather than if you want to test what it's like to track the GPS coordinates of an Apple Watch moving through space.
You kind of have to actually move the Apple Watch through space for eight hours, which is fine if you're like me and I'm training for a long event. And so I very often have the opportunity to do that. But it is going to be really frustrating when you're supposed to be going out for like an eight-hour –
And, you know, an hour into it, you realize this is a bad build, like something's wrong, something, you know, something you did isn't right. And just like, well, I guess this test didn't work. And I'm just, you know, just gonna have to try again the next time I go for a long hike.
Because you just have to design these experiments and try things out and be experimental, be creative with it is something too. I think like you were saying is it isn't always going to be the thing that you think. And so to some degree, you have to isolate different variables. Experiment and test them separately and then make sure that you're trying to be holistic in that approach of understanding that it isn't necessarily the obvious thing that is going to be the most power consumptive.
Sometimes it's going to be other things. And so you'll turn things off incrementally or experimentally and try and isolate. Like this variable seems to be where things are, you know. where things are getting stuck or where there's extra power consumption where maybe there doesn't need to be or if it's above what it seems to be. And so that's kind of what we end up having to do, or at least as far as I can tell, that's what you're going to do. And there's no easy way around it because...
In order to really know the real life results, you have to actually go out into the real world. I love the simulator for testing my core location stuff. We've talked about SimGenie before, which is a great tool. for being able to reproduce GPS tracks. in the simulator so you can actually test your logic for that. And that's useful to some degree in terms of I've done a lot of work that improves my battery life by making my algorithms more efficient, which I can do in the simulator.
But ultimately, at some point, you're going to have to lace up your shoes, get out, and actually test it in the field or try it in the situations where it's going to be used. Because if you don't, you won't actually know what the battery life will be used in practice. Yeah, exactly. And that's the answer to so many of these things. A lot of times it just depends because battery life, with the big users of it, like the screen brightness, the speaker, the usage of GPS. Those are big users of it.
when, you know, like, okay, using the radios is power inefficient well if you can like use them less frequently maybe batch your request they don't have to like constantly wake up you know connect to whatever and then go back to sleep like that that that's kind of more obvious Sometimes when you're dealing with optimization of processor usage or load on the processor,
Sometimes it's less obvious what will matter and what won't. Because the processors are really complicated. And this is partially true of the radios, too. The radios are also very complicated. But the processors, I think more than anything... This is where you have to often just try stuff, and you can't rely on the little meter in Xcode that says power use, power impact low. That's a very coarse mechanic.
Because the reality is the processors oftentimes they're doing such complicated things. They're managing their power in such a complex way. They're splitting. task between the efficiency cores and the performance cores. They're ramping up the clock speed and then ramping it back down many times per second. They're doing these things that sometimes What you think will be inefficient or efficient won't be because sometimes they can – like for instance, if you have –
a certain amount of computation to do, and it doesn't really matter when you do it. You might think, let me do a little bit at a time so the CPU is only 5% used for a long time. Well, that actually might be less efficient.
than using 100% of a core for five seconds and then stopping or whatever. Because that can actually... Some core might have to stay awake longer than it would if it's doing... a long-running small task as opposed to being able to do a big burst of work and then go to sleep for a longer time after that. This pattern is called race to sleep. And the idea, oftentimes, it is more efficient with modern processors. Often it's more efficient to...
Just do the work really fast at maximum power and then stop and then sleep for as long as you possibly can before you ask it to do more. Sometimes the opposite is true, depending on whether that task can run on an efficiency core or not. As you were saying, Dave, there is really no better answer than you got to just test this stuff and be willing to experiment. And again, to many apps, this won't even be necessary. If people are just launching it briefly, no problem. Do whatever you want.
Once you have something long running, if it's some kind of background thing or long foreground usage, this is going to start to matter a lot. And people are going to notice, like, hey, does my phone get hot while I use this app? Or does my battery sync dramatically? Like, that does matter to people a lot. And if you'll allow me a brief rant. I'm very frustrated by how the tooling we have for this is because ultimately, like I said, we have to just do these tests.
And I feel like it feels very... frustrating in like whatever the 18th year of iOS's existence that so often I'm falling back to like in the console logging the battery percent as reported by the system as the best tool I have for a lot of these things. And as a side rant, it is very frustrating that debug build... You know, I'm clearly a developer doing this thing. We'll still have the masked battery percentage reported to it, which is like rounds to the nearest 5%. Oh, yeah.
Which is like, why are you doing that to me? That's unnecessary. I understand it makes sense for fingerprinting reasons and things in production apps, but come on. I wish that would just give me the actual number so that I don't need to run things for twice as long to make sure that I'm averaging out. the weird sort of rounding issues and errors there. But nevertheless, it's something that I was just really surprised by when I went and wanted to do...
like power analysis and these kinds of things. Like I expected there to be like a really detailed instrument in Xcode that would give me a lot of the detail that I might want. And as far as I could tell, there's some very basic...
things. And some of them have sort of come and gone over the years. Sometimes I find these articles about tools which don't seem to exist in Xcode anymore, but ultimately... I kind of get a lot of t-shirt sizing with the power consumption is the best you can do where it's like you're doing small, medium or large amounts of battery use right now, which is like, okay.
But if I'm trying to optimize things more specifically, it'd be really helpful if there was some amount of more like you're using so many, whatever, like, you know, milliwatt hours or milliwatts per hour, whatever, like some measurement. that I could actually be basing something on or...
Things like that. And I understand, obviously, measurement always has overhead. And so maybe that's why some of these things aren't in there. But in a lot of circumstances, I wish I could take on that overhead and understand, obviously, it's doing a lot of extra work. So the battery percent.
would no longer be representative, but the measurement of what my app was doing or what the overall system power draw is could be consistent between runs or those kinds of things. And it was just something that really surprised me.
where I feel like a lot of instruments is amazing and continues to get better every year, it seems. But power and battery life particularly just didn't seem like something that... there was nearly as much detail and ability for us to dig into, and particularly if you're doing anything with watchOS. Like it was shocking to me how challenging it was to even some of these like T-shirt sized power usage things.
don't seem to apply on watchOS. And so on what I ended up doing for a bunch of my apps, like I would say, I was like combining, stripping these things down to their smallest parts. And sometimes I would just run the library that I had written on my iPhone and see how it works there where I could actually get some bit more data there and run and have a slightly faster sort of testing cycle there.
And obviously the iPhone is not an Apple Watch, but things that are bad on the iPhone are likely also going to be bad on watchOS. And so I was able to catch some issues that way. But it was just one of these things that I was very surprised by. And maybe it is to your point that a lot of... There's certain kinds of apps that just don't
need to worry about power consumption dramatically. I mean, in a way that like Widgetsmith is an app that I don't think I've ever really paid much attention to its battery consumption because it's an app that users tend to open. do an operation for a short period of time, and then close the app, and that's it. And so it's very unlikely Widgetsmith's ever going to show up in a very heavy, heavy way in a user's... battery meter but in the places that I do use it
boy, do I wish there was some kind of way that I could have a better insight into it. I mean, it reminds me of those things you'll see when people are playing video games and they have a little HUD that has the current frames per second.
Details about the rendering. It's like, boy, do I wish I could just have a little HUD on the top of the screen that was telling me the current power consumption as it's being drawn from the battery or something. That would have made my life so much easier and saved me so much time in this process.
You know what you should do? You should scale it up to be able to run on a Mac Mini, spin up like, you know, a hundred of those processes and just put a kilowatt meter between the Mac Mini and the wall. You can see how much power it's pulling. Just take the battery completely out of the loop and just do it purely by the amount of power draw that is happening on the computer. There you go. Problem solved. I don't know why I didn't think of that.
One other thing I wanted to mention is programmatically adapting to low power mode. So this is one thing you can do on the process info object. There's an is low power mode enabled property. And there's some notifications there when it changes to if you want to monitor it that way. This is one of those things that I thought was a great idea. And so I tried it. And it turns out, so my thinking was, How about when the device is in low power mode for Overcast, maybe I do things like...
you know, reduce the frame rate of animations that I'm generating manually, like those waveform animations I had, like in the pause button. Maybe I've reduced the frame rate of that. Maybe, you know, more importantly, maybe I will reduce the frequency at which I sync back to the server.
Or maybe I would defer tasks like downloading – like checking for artwork changes for podcasts and downloading new artwork. Maybe I defer those until it's not in low power mode. And that was a great – those were all great ideas and they all failed. except for the animation frame or anything. I think I actually kept that one. But everything else I had to abandon because...
The reality is many users out there basically use low power mode all the time. It's a very, very common usage pattern that people just – like every day they just pick up their phone and flip on low power mode. You shouldn't change anything in low power mode that would negatively affect your app if that was always the condition under which it was running. So for instance, I was deferring artwork updates. This was years ago now. But I had this idea, oh, I'll defer artwork updates.
And I was getting complaints from people saying... I have the wrong art for this podcast, and I would occasionally begin with some of them, and oftentimes I would get to this question, and they were actually running low power mode all the time, and so it was just never having a chance to update. This is something that you can do, you know, you can make minor tweaks to what your app is doing in response to low power mode, but I would not recommend making major tweaks.
And even if you tell the user, maybe if you have a banner up saying sync deferred because of low power mode or whatever, that's not going to solve your problem because that's not going to make people change their power using their phone. That's just going to make them not use your app or complain about it. So be careful what you change in response to that and make sure it's something that the ramifications aren't too bad if you are never running in regular mode.
Yeah, and I think I ran into a similar sort of pattern in this update I'm working on, where I was thinking, oh, if the user's in low power mode on their watch, I should do different things. And I think what I ended up settling on... is that for the same reasons you're describing, I think it would be better, and this is sort of where I'm going, is there's a mode inside of the app that does different things and is the sort of...
The expedition mode, the long run, long walk kind of mode that the user is specifically opting into rather than responding to the system flag, which can be on for a variety of reasons. It's something that the user is saying, hey, I want to maximize my battery life. I want to turn this on. And they're making a positive choice to do that rather than assuming.
that the reason they have low power mode on is because they want those low power features. They may want all of the capability, but, you know, just... Or turning that on out of habit or out of sort of other reasons or other needs. Or similarly, like I think of in watchOS, there's some switches that you have to go and turn on if you want to enhance the battery life in low power mode. Like it can take fewer.
GPS readings or heart rate readings in the main workouts app. But you have to go and turn that on rather than the system just assuming, oh, you're in low power mode. I'm going to do that for you because you just don't want to make assumptions and you don't ultimately.
In all of this, battery life is a tension between user experience and capability and power. And like you just there's a certain tradeoff that you have to make there and be careful about making assumptions about that is exactly right. Like you don't want to assume. that the situation is any one state, whether that's low power mode here, whether that's network connectivity, whether it's screen brightness, volume on the speaker.
All of these things are just these variables we're trying to balance between. And if we can get the balance right, we can improve the overall user experience by giving a user more time with our app, which is ultimately wonderful, but we just have to be thoughtful and careful to arrive at that place in a way that makes everybody happy. Thanks for listening, everybody. And we'll talk to you in two weeks. Bye.