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: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: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:15
◼
►
[BLANK_AUDIO]