Under the Radar

120: Productivity by Inefficiency


00:00:00   Welcome to Under the Radar, a show about independent iOS app development.

00:00:04   I'm Marco Arment.

00:00:05   And I'm David Smith.

00:00:06   Under the Radar is never longer than 30 minutes, so let's get started.

00:00:10   So today we wanted to kind of talk through -- when I was first looking for this topic,

00:00:16   I think it was sort of like calling it like tips and tricks or like things that we use,

00:00:20   like little approaches or unusual things that we may use that could be useful to share.

00:00:25   But I think as we thought about it more, it's just there's some interesting approaches

00:00:28   that are being made possible now or things that we have done in the past that I don't

00:00:33   necessarily would say are best practices, aren't things that I would necessarily recommend,

00:00:38   but are things that nevertheless I think we found that we do in our apps that has been

00:00:42   useful or productive or allowed us to do stuff that, you know, just lets us get on with things.

00:00:49   And I always like kind of sharing these types of little things, not so much that they'll

00:00:54   necessarily apply directly to you or your application, but they can kind of get your,

00:00:58   you know, they can get you thinking.

00:01:00   Or later when you encounter a problem that is similar or adjacent, you know, you can

00:01:06   have something in the back of your mind as maybe like, you know, here's this different

00:01:09   approach or this alternative way to look at a problem that might be helpful.

00:01:14   And I think the first category of these that I think is fun to talk about are things that

00:01:19   we can now do in our applications because the hardware has just gotten so fast that

00:01:26   there is certain things that in the early days of iOS development where, you know, on

00:01:31   the first, you know, the original iPhone or the 3G, 3GS, like back in those days, like

00:01:35   there was a period of time really before the A series chips is probably a good way to say

00:01:40   it, like where you're for the performance of your code was very directly tied to the

00:01:47   quality of your user experience.

00:01:49   I mean, these are back in the days where I remember where like Lauren Brikter's crazy

00:01:54   tricks for making UI table views scroll quickly in Tweedy was like magic.

00:01:59   And like that was the kind of level of things where you were really optimizing for every

00:02:04   little thing.

00:02:05   And that's what it took to have a well-performing, good user experience type of application.

00:02:11   But thankfully, that's not really the case now.

00:02:14   I think for the most part in most of my applications, like the baseline performance that I have

00:02:19   to, you know, deal with is iPhone 6 or equivalent, which is surprisingly capable, which is surprisingly,

00:02:27   you know, able to do a lot of things pretty quickly.

00:02:31   And so, you know, it allows you to do stuff that isn't necessarily the most efficient

00:02:37   way to do it but will still work just fine, will not really hit the user with a lot of

00:02:42   delay, but allow you to write code either that is very clear and straightforward for

00:02:48   you as a developer or maybe very reliable.

00:02:51   And as an example, like so the first one I was just going to talk about is a recent

00:02:55   thing that I did in Panometer++ where I wanted to make it so that you could transfer your

00:02:59   steps from one phone to another.

00:03:02   And there's a lot of crazy ways you could do that.

00:03:04   I could take the actual, you know, SQLite database and move it over and do some kind

00:03:07   of merging thing.

00:03:08   Instead, what I did is I just had the, you know, on the sending device, you can just

00:03:14   export all of the data into a giant JSON blob.

00:03:18   I take that and compress it to, you know, it's reasonably small and because it's, you

00:03:23   know, text, it compresses very, very well.

00:03:25   And then I just send it over to the new phone.

00:03:28   And then on that side, I basically simulate as though that was core motion data coming

00:03:34   into my regular step management system and it processes it and imports it into the system.

00:03:41   And because the core motion system is already designed to make sure it doesn't duplicate

00:03:46   data, making sure that it doesn't lose data or have any weird things happen, like that

00:03:50   is the core of the app, I know it's reliable and it's slightly inefficient.

00:03:55   Like it's a really, you know, round tripping data through text and all these types of things

00:04:00   that is probably not very efficient, but even on a 6S or a 6, you know, an import of all

00:04:06   of the data that in my database, which is the most data that any one user could have

00:04:10   because my database goes back to the first day the app was created, it still takes like

00:04:16   a second.

00:04:17   And so it's fine.

00:04:18   And it's kind of fun to just be able to like do this.

00:04:20   And I'm now very confident that this works because it's all it's very straightforward

00:04:25   and there's nothing clever going on.

00:04:26   I'm not doing some crazy, you know, file encoding that I'm coming up with myself to,

00:04:31   you know, do my own binary file encoding.

00:04:34   It's just a JSON file compressed and then uncompressed and then pushed into one of the

00:04:40   most tested part of my applications.

00:04:42   And it works.

00:04:43   And I can do it because hardware has gotten so fast.

00:04:46   In many ways, JSON usage itself has exploded in the world of programming in part because

00:04:53   everything is so fast.

00:04:54   Like, you would never have a format like JSON being as dominant as it is today even 10 years

00:05:01   ago because the hardware, like, all the effort to serialize and unserialize the JSON blobs

00:05:08   would have been not worth it.

00:05:09   You know, you'd have something more like what I've never used protocol buffers, but

00:05:12   I think it works more like a binary format.

00:05:15   And you have, you know, a few other different ways to do binary formats where like, you

00:05:19   know, you don't need to be reading like a text readable serialization of the data.

00:05:24   You can like actually use binary encoding and make things way faster.

00:05:27   But these days, it's so fast to decode and encode JSON and then gzip it for size.

00:05:34   Like, it's so fast to do that that you don't need anything as low level optimized as something

00:05:40   like a protocol buffer.

00:05:41   Like, you can just use JSON.

00:05:43   Even the idea of when you process large blobs of JSON, you know, old formats used to be

00:05:50   defined so that you could stream process them.

00:05:53   Like, even the first XML processors, they were called SACS processors.

00:05:57   And like SACS libraries would basically stream the XML nodes to the processing app.

00:06:02   So you wouldn't need to be holding an entire DOM in memory.

00:06:05   So if you had like a large XML document, you wouldn't have to hold the whole thing in

00:06:10   memory as you're processing it.

00:06:11   With JSON, I'm not aware of any streaming JSON processing libraries.

00:06:16   With JSON, you just decode the entire thing into memory, no matter how big it is.

00:06:21   And, you know, computers are so fast and memories are so large these days that that's rarely

00:06:26   a problem for anybody.

00:06:27   It's just one of those little luxuries that like, you know, we are able to be far more

00:06:31   productive these days than we used to be just because we don't have, like, there are things

00:06:36   like that that used to hit limits or be too slow.

00:06:39   And these days, that almost never is the case.

00:06:42   Yeah, and it's just such a lovely little thing.

00:06:45   What I love about it too is so many of these things that we're going to talk about, I think

00:06:49   what I love about them most is that they make the development process, it's like, I think

00:06:55   it was a phrase often in the early days of Rails was used there, it was like developer-oriented

00:06:59   programming, where it's making it easy to write a good app by making it so that it's

00:07:06   easy to understand the process.

00:07:08   And what I love about this is, like, it's, you know, if I'm, as I was testing it and

00:07:13   as I was evaluating it, if at any point I want to, like, see what's in the, you know,

00:07:16   see what's in the transfer file, well, I can open it up in a text editor and look at it.

00:07:21   There's nothing complicated about it.

00:07:23   And any time I, you know, like, by making it so straightforward, that really just helps

00:07:28   things go so much more quickly.

00:07:30   Or, you know, even in the debugging process, like, I can, at any point, you know, as it's

00:07:34   importing, I can very quickly just see exactly where we are in the giant array of step data

00:07:39   that's coming into it.

00:07:40   And, like, by rather than trying to optimize for absolute efficiency, you know, it's optimizing

00:07:47   for clarity and reliability in terms of I'm very sure that it's, you know, it's going

00:07:53   to work, because it's so simple that I'm reducing a lot of weird edge cases and problems that

00:07:59   I might run into if I tried to do it in a more sophisticated or complicated way.

00:08:04   - Oh yeah, and this applies also to a few topics we've covered before, like, I believe

00:08:08   we talked about design by programming, or design by programmers, or design by math,

00:08:12   which is basically like procedural image generation or procedural colors and things like that.

00:08:18   And the whole reason that you can have, like, you know, like what Overcast does is almost

00:08:23   every icon or image in the entire app is drawn on demand.

00:08:28   So there aren't a bunch of image files in the bundle, like there's actually, almost

00:08:31   all of them are drawn upon request by just helper functions in my appearance manager

00:08:36   class, and, you know, the app requests, you know, the download icon with this number of

00:08:42   downloads at this size with this color, and this function generates that.

00:08:46   And, you know, in the olden days, not even that long ago, just like in the first few

00:08:50   iPhones even, that would have been too slow, like the app would have taken too long to

00:08:54   launch or taken too long to go between screens because it had to draw all the icons.

00:08:58   These days, everything is so fast that that is worth doing on every launch because you

00:09:06   just don't even notice it, and it saves me the work of having to manually render out

00:09:11   images every time, like, there's a new phone size or something else, although there is

00:09:15   one area where that's not the case for me, and that is the watch.

00:09:19   And that's actually probably not even true anymore, like the Series 3, but like the

00:09:23   original Series 1 watch was so slow that WatchKit was based a lot on the use of static

00:09:29   images instead of dynamic generated things or animations, and as you know very well,

00:09:34   I'm sure, and so, you know, there is one area in my app where I do generate the images

00:09:40   for the watch, and it's just a preprocessor macro that I set for this file that's not

00:09:46   usually included, and when I set that, upon launch in the simulator, it creates a

00:09:51   folder on my desktop full of all the images that I need, and it just calls the dynamic

00:09:55   rendering functions that the rest of the app is using with the parameters that the

00:09:59   watches will need and the two sizes for the watch and in whatever resolution they need.

00:10:04   And it's so funny, like, I launch this on my computer, I just, you know, hit this

00:10:07   preprocessor flag, hit build and run, and the second the app launches, this folder is

00:10:13   on my desktop with like 40 images in it, they're all done.

00:10:16   It just takes so little time to do this stuff now, especially on a desktop, but

00:10:20   that's the only place in my app where I ever really have like static images, the

00:10:24   rest of it is all dynamic, and not only that, I also do dynamic color manipulation,

00:10:30   and this is a little trick that I suggest everybody do if you build apps this way.

00:10:36   The way Overcast does its themes, and the reason why I was able to make the black

00:10:42   OLED theme during a single airplane flight and have it be pretty much done is because

00:10:49   Overcast, in my appearance manager, I define a small handful of like base colors,

00:10:56   and then all the other colors that are in the theme are dynamically generated from a

00:11:01   category that I write on UIColor, and in fact this is publicly available in my

00:11:05   FC utilities library. UIColor, I have these categories that basically you can,

00:11:10   on an existing color you can call, you know, FC_modifyHSBA, modify the hue,

00:11:17   saturation, brightness, and alpha of any color in a little simple block that you pass

00:11:22   to the function, and the HSBA are all passed as in-out pointers, so you just call

00:11:28   the block, it can have one line, it can be like, you know, star A equals 0.5, and then

00:11:33   the resulting color will be whatever color you pass to it with the alpha set to 0.5.

00:11:37   And so I, in my appearance manager, on every launch of the app, or every time you

00:11:42   change the theme even, I have all the colors being generated dynamically based on

00:11:48   the like three or four base colors of that theme, and so all the other colors are

00:11:52   like, alright, take the base color, but set the saturation, you know, cut the

00:11:56   saturation in half and increase the brightness by 25%, like stuff like that.

00:12:00   That's how the entire theme is generated, so for me to make a new theme, it takes

00:12:04   almost no effort at all, which is kind of funny since I've only ever made three,

00:12:07   but it's all this work, all this dynamic generation, so that I can save myself

00:12:14   small or big amounts of work down the road, and it seems wasteful to do it that

00:12:19   way on the phone, to be like rendering these colors and images on every launch

00:12:23   of the app, but in fact it's so fast, it really doesn't matter.

00:12:26   Yeah, and I mean, it's another thing it reminds me of is how like I'm sure there

00:12:32   are OpenGL-based graphing libraries that exist, like for like graphing data,

00:12:38   and I'm sure they're incredibly efficient. I'm guessing at that, but I'm sure

00:12:43   they exist, but a lot of my applications involve graphing, and I just do all the

00:12:47   graphing and core graphics, and it's fine, and like you want to do dynamic colors?

00:12:51   Great. You want to animate them? Sure. Like it's core graphics itself, like

00:12:55   what you're saying, like you're generating sort of assets, and I'm generating

00:12:59   charts, but a lot of that stuff is still just like, yeah, it's fine, and I mean,

00:13:03   maybe from an energy consumption perspective, it's not ideal, like in theory

00:13:08   it could be using less, but it's one of these things that in practice it doesn't

00:13:12   seem to really, it's not, it doesn't really matter in practice, and it makes

00:13:18   the process of developing and the usefulness of the application increase so

00:13:22   much that like it's a shortcut that I think is justified in taking.

00:13:26   Oh yeah, I mean, you know, it's important to, like if you're, as you're coming up

00:13:32   with something, or as you're faced with a problem, you know, a lot of people when

00:13:36   faced with the issue of how do I efficiently draw, you know, this big image,

00:13:40   or even like, like when I was doing my visualization of the frequency graphs in

00:13:44   the overcast, like play bars thing in early version of overcast, or even now,

00:13:48   when I do the kind of peak level metering in the pause button icon in overcast as

00:13:53   things are playing, it's, I tried OpenGL first, because I thought for sure this is

00:13:59   not going to be fast enough if I just use the, you know, the quartz drawing

00:14:02   commands, like UI Bezier path and stuff like that, or even then you think like

00:14:06   maybe I shouldn't use the objective C wrapper, maybe I should just go right to

00:14:09   the raw C functions, and it turns out everything is so fast that it didn't

00:14:13   matter, and I made the OpenGL prototype, and I made the quartz prototype, and

00:14:19   they ran at about the same speed, because quartz is really fast and optimized

00:14:22   anyway, and the hardware is so fast that it really didn't matter, so I, I rendered

00:14:26   the whole thing in quartz the whole time, because it saved me the trouble of

00:14:29   dealing with OpenGL. It was wonderful, and, and like, and there was, there was

00:14:33   also a situation where, so for reading all the binary formats that I read in my

00:14:39   apps, so overcast when it reads file data to see like, you know, to determine

00:14:44   whether something is an MP3 or an AAC file, when it reads like the, the, the

00:14:51   QuickTime mauve format to, to see like, can I stream this file or not without

00:14:56   having downloaded the whole thing, or even like, you know, when forecast

00:15:00   generates ID3 or reads ID3 tags, there's a lot of processing of binary data that

00:15:06   I do now in my apps, and you can do this by like raw C functions, and it can be

00:15:13   really fast, but you're gonna have a lot of bugs, a lot of crashes, and potential

00:15:17   security problems if you do that with like, you know, with C functions, I mean

00:15:20   almost every like security bug that's found in like file format parsing or

00:15:24   things like that, like if oh, you pass a bad JPEG to this library, and you get

00:15:27   root access or whatever, like that's almost all because of mistakes in low

00:15:31   level C code, and so today we have hardware that is just so fast, and the

00:15:38   Objective-C libraries that are just so good, that I, I wrote my own entire class

00:15:43   for parsing and writing small binary data formats like that, that uses NS data

00:15:49   blobs for everything, and it's ridiculously inefficient, because it's

00:15:53   like it's using Objective-C primitives and Objective-C types and arrays and NS

00:15:58   data and NS dictionaries and stuff, and you know, it's like to read a byte, I'm

00:16:02   calling an Objective-C method that reads like a one byte NS data blob, converts it

00:16:07   back to an integer or whatever, and then advances the pointer by one and creates

00:16:10   a new NS data or whatever, like it's completely inefficient in like raw low

00:16:15   level programming terms, but it allows me to read and parse and write binary

00:16:20   formats, which is something that's traditionally been sort of hard to do, in

00:16:24   a really easy way, with very little of my time, and in a way that's almost

00:16:29   completely bug free at, for like low level memory bugs, like I can't overrun a

00:16:34   buffer, I can't like try to read a void, like a pointer that's past where it

00:16:38   should be or anything, like I can't do that, because it's using Objective-C

00:16:42   primitives instead of raw C calls everywhere, and it's just really nice, and

00:16:47   you know what, it's fast enough, it doesn't matter at all, like the ways I'm

00:16:52   using this and the hardware I'm running this on, it's so fast that I can do it

00:16:55   in this inefficient way using these like relatively high level objects and APIs,

00:17:01   because that saves me time, and it doesn't really matter what the computer is

00:17:06   doing under it, because it's so fast the computer barely even notices.

00:17:09   Yeah, and it's lovely too that it's, I think, I will say that I have noticed

00:17:13   that over the last few years, I have less problems with things like with

00:17:18   crashing bugs and, you know, weird esoteric like application errors and

00:17:24   problems, and I think in many ways, a lot, I think there is a correlation between

00:17:29   a increase in performance and so a less of a requirement to drop down the levels

00:17:36   of abstraction to squeeze out every inch of performance, and instead I'm able to

00:17:41   work at a higher level of abstraction where the problems are easier to

00:17:44   understand and the types of problems I'm going to run into are more conceptual

00:17:49   rather than technical, and I think it's definitely helped that most of my

00:17:53   applications now are like 99.9% crash free, like if I look at my stats, and

00:17:59   overall, I'm sure most users care way more about that, and that's a much better

00:18:03   impact on their overall use of the application, you know, than trying to

00:18:07   squeeze every ounce of performance out.

00:18:11   We are sponsored this week by FreshBooks. To all the freelancers out there, you

00:18:15   know how important it is to make smart decisions for your business. Our friends

00:18:19   at FreshBooks can save you hundreds of hours with their cloud accounting

00:18:22   software for freelancers. It is ridiculously easy to use. By simplifying

00:18:26   tasks like invoicing, tracking expenses, and getting paid online, FreshBooks has

00:18:30   drastically reduced the time it takes for over 10 million people to deal with

00:18:34   their paperwork. FreshBooks does amazing, smart things to help you save time.

00:18:39   So for instance, they automate late payment email reminders so you can spend

00:18:43   less time chasing down payments and more time doing your actual work. And when

00:18:47   you email a client an invoice, FreshBooks can show you whether they've seen it,

00:18:51   which puts an end to the guessing games and the awkward conversations. And they

00:18:54   have all sorts of new features, like a new projects feature to share messages

00:18:57   and files with your clients, and there's so much more with FreshBooks. Check it

00:19:01   out today. If you are listening to this and not using FreshBooks yet, now is the

00:19:05   time to try it. FreshBooks is offering an unrestricted 30-day free trial for

00:19:09   listeners of this show with no credit card required. All you have to do is go

00:19:13   to FreshBooks.com/Radar and enter "Under the Radar" in the "How did you hear

00:19:18   about us?" section. We thank FreshBooks for their support of this show.

00:19:21   So another kind of trick or thing that we can do in our application are the

00:19:26   kinds of little tricks we can do because most users won't notice or care about

00:19:32   a change. An example of this I can think of is in the early versions of

00:19:39   Pedometer++, I had a tip jar where users could give voluntary donations, I guess,

00:19:46   to me for using the application. And I structured that as a consumable in-app

00:19:51   purchase because I wanted to allow people to make multiple tips if they were so

00:19:57   chose. But the awkward thing about that is there's no way to restore an in-app

00:20:04   purchase that is consumable. Like, in the concept of the in-app purchase system,

00:20:09   you can restore and kind of get a receipt and a transcript of all the users'

00:20:14   past purchases if they're non-consumable. But if it's consumable, they don't come

00:20:19   back. When they get a new phone and they set it up, there's no way to know that

00:20:25   they did it. And it's a slightly awkward situation. You know, someone who has

00:20:31   chosen to give me money, I want to give them the best situation. And so this is

00:20:36   slightly different now in the application because of some changes around what

00:20:39   Apple required. But in the old version of the app, there was a button that said

00:20:43   "Restore past purchase." And when you pushed that button, it just assumed that

00:20:49   you had previously made a purchase and unlocked the--or removed the ads from the

00:20:53   application and said "Thank you." It didn't actually do anything. There was no

00:20:57   validation. There was no check. It just--if you pushed--if you were, as a user,

00:21:00   thought to push that button, it removed the ads and assumed that you had made a

00:21:04   purchase. And it was like that for a couple of years. And it was never a

00:21:09   problem. And like, you know, every now and then I'd have someone who's, you know,

00:21:14   writes in a support email and asks about--you know, it's like, "Oh, I purchased

00:21:18   before. What did I do?" And I'd just push the button and it'll come back. And

00:21:21   honestly, not even that many people would ask that now, but it's one of these

00:21:25   things of I made the choice that there's--it's possible that at some point

00:21:29   someone may push the button who didn't actually buy anything. That certainly is

00:21:34   a possibility. But the reality is most people aren't going to notice or care.

00:21:38   And it's way better in this situation to just say that, you know, more likely

00:21:43   than not it's going to be fine and I'm going to end up way better by making my

00:21:47   best customers, the people who have chosen to give me money happier is way

00:21:53   better for me than to do it the other way around. Now it's slightly more

00:21:57   complicated because Apple had a policy change where things like TipJars had to

00:22:02   not use consumable in-app purchase. And so now I have a--or at least not

00:22:06   initially. And so now I have this two-phase thing where your first purchase

00:22:10   is a non-consumable in-app purchase that I can then restore. And then your

00:22:15   subsequent ones are consumable but that doesn't matter because I still know you

00:22:19   have one. But it's just--initially I didn't want to make that complexity. So I

00:22:23   didn't. And it was fine. And for years it was great. And so, you know, it's like

00:22:29   one of these little tips and tricks where you can kind of look at it and say,

00:22:32   "Do I really care about the small chance of this being problematic versus the big

00:22:38   chance of this being beneficial?" Because most of you people just won't notice or

00:22:41   care.

00:22:42   - Oh, yeah. Like in the Overcast app, the iPad app is pretty hard to lay out

00:22:47   because I have a lot of code sharing with the iPhone app and iPad has way

00:22:51   different aspect ratios for different things. One of the problems is I really

00:22:55   couldn't figure out a way to lay out the ad in Overcast on the now-playing screen

00:22:59   on the iPad. So I just don't show the ads on the iPad because it's only like 6% of

00:23:05   my user base and I decided losing 6% of ad views is worth the benefit of not

00:23:11   having to write a ton more code on this right now because it wasn't going to be

00:23:17   worth it for me. You know, when money's involved, it's a little hard to do things

00:23:23   like what you're saying or like not showing my ads here. But you always have

00:23:27   to think about like how much of my time is this actually worth for like what is

00:23:32   the actual risk or loss of not doing it here.

00:23:35   - Yeah, and it's, I mean, certainly we have the wonderful situation of being

00:23:41   like completely in control of these choices that we make. And we can decide

00:23:44   that if we kind of want to just bypass this complexity and take a shortcut

00:23:49   because like basically if the user, it's like these types of tweaks or tricks are

00:23:54   just like is the user going to complain? You know, I doubt they are. Like your

00:24:00   iPad user is going to say like where's my ads? I want my ads. Like if anything

00:24:04   they're just not going to notice. I mean, there's always ways too that you can

00:24:08   kind of similarly, like little tricks that I use that are maybe more positive in

00:24:12   a similar kind of vein where like I always love keeping a kind of, I keep a

00:24:17   vague metric of how much someone has used an application. And I tend to just

00:24:23   like store this in a user defaults value that, you know, I increment over time or

00:24:27   something like that to try and allow me to, you know, change things in the

00:24:31   application based on if they use it a lot. So like for example, the first time

00:24:35   you ever launch a lot of my apps, if they have ads, the ad won't show the first

00:24:40   time because there's A, there's some weird complexities around that for like,

00:24:46   it's you never, you know, if the app isn't working correctly, you know, say like

00:24:52   they, in a lot of my apps require users to give permissions. Like I need to get

00:24:56   access to your motion data or your health data. And the app will kind of be in some

00:25:01   ways be kind of broken if you haven't given it permission. If that, you know,

00:25:05   when the app first launches and it asks you, if you say no, the app isn't kind of

00:25:09   this weird broken, that's not broken, but it won't really work correctly. And I

00:25:12   don't really like the thought of like having these kind of these error messages

00:25:15   and things saying, hey, you need to go to this place and, you know, turn on all

00:25:19   these permissions. And at the bottom, I'm showing an ad. Like I don't like the

00:25:22   feel of that. And so it's like, well, I'm just not going to show the ad until I'm

00:25:25   pretty confident that the app is working and, you know, is creating an experience

00:25:29   that you're going to want to come back to. Or even more similarly, like a little

00:25:33   trick that I use in pedometer is as soon as you have used it enough that you have

00:25:39   more than enough data that it will fit more than just a one screen of data. I

00:25:44   slightly wiggle the bar graph to show that it moves to encourage you to scroll.

00:25:50   And then the first time you scroll and see past data, you know, I again, I mark

00:25:54   this little like total use thing to say that now they've used that. So, you know,

00:25:58   stop wiggling the bars. But it that kind of these little tricks to just try and

00:26:03   make the app gradually get better and better for people as they go. Yeah. And

00:26:08   there's all sorts of things you can do like that. Just people might not notice

00:26:12   or care about. Like, for instance, if you have an annual subscription, you don't

00:26:18   have to actually write the code that tells people their subscription is going

00:26:21   to expire for a year after you launch it. Yeah. Things like that. Like, and you

00:26:27   know, like, and there's a couple of shortcuts that I have also taken where

00:26:30   it's kind of like, let me see if I can get away with something like by basically

00:26:33   like, let me try it and see if no one complains. So one of the more recent

00:26:39   examples of this is Overcast basically does not support rotation on the iPhone

00:26:44   10. There's a couple of bugs that will occasionally let it happen, but it

00:26:47   shouldn't be happening. For most people, it's not happening. On the iPhone 10,

00:26:50   rewriting all the view controllers and everything to support all the inset edges

00:26:56   and stuff was such a big job that I decided, you know what, let me just

00:27:00   launch my iPhone 10 update with rotation disabled only on the 10 and I'll see

00:27:05   how many people complain. And it's been almost none. Almost no one has complained.

00:27:09   And so I think I'm going to actually just disable rotation on the 10 permanently

00:27:13   because it's, again, it's a lot of work to do and then to maintain. And also the

00:27:18   rotation is really buggy in iOS 11, just in general. So like that's a lot of stuff

00:27:23   that I would have to do for right now by not doing it. No one seems to care.

00:27:29   So I'm probably just going to not do it.

00:27:32   Yeah, and I think what's great about that is it's an example of these things

00:27:35   where I feel like it's so easy, you know, when you're developing something to

00:27:40   imagine all of the possibilities and the situations that people may want, all of

00:27:47   the things that they could potentially think about or might possibly desire.

00:27:52   And you can imagine all these scenarios and you can kind of build them up in your

00:27:55   mind to be bigger than they actually are in practice. And I've had a similar kind

00:28:02   of thing where people often don't notice or don't care about some of the things

00:28:07   that you think they might. And so building ahead of time some of those things

00:28:11   doesn't make a lot of sense. And then also it's understanding that people,

00:28:16   if you build all these things ahead of time, in this case, if you did all the

00:28:25   crazy work on rotation, you may not actually garner any better, more goodwill

00:28:31   if a) most users don't see it and b) the users who do see it will just assume

00:28:35   that of course it's there for sure. Whereas the opposite in a weird way is

00:28:40   if you didn't launch with rotation on the iPhone 10 and you get lots of people

00:28:44   who ask for it and then you add it, in a weird way you actually come out ahead

00:28:48   because then suddenly you just gave them something they want. Which is, in a

00:28:53   weird way, it's a funny way to structure it but you actually potentially

00:28:57   engender more goodwill by limiting it initially and then subsequently fixing it.

00:29:03   And that's a dangerous game to play but it certainly is a game that you can play

00:29:07   and sometimes win as a result.

00:29:09   That's awesome.

00:29:10   Alright, well thanks for listening everybody and we'll talk to you next week.

00:29:14   Bye.

00:29:15   [BLANK_AUDIO]