WWDC2015 Session 230

Transcript

X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
[Applause]
>> BEN ENGLERT: Good morning.
My name's Ben.
I am an iOS Performance
Engineer, and today we are going
to talk about "Performance
on iOS and watchOS."
So we are going to
start by telling you,
why should you think
about performance?
If you've never thought
about performance
in your app before,
why start now?
Hopefully I'll convince
you to stay in your seats,
and then we can move on to,
how should you think
about performance?
It can be like a very broad
and sometimes daunting topic,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
It can be like a very broad
and sometimes daunting topic,
but we are going to break it
down into a couple of categories
and give you a few
specific strategies
for improving the performance
of your app in those areas.
And finally, you're
hopefully really excited
to bring native code to watchOS
2, and we are going to dig
into what you can do to get
the best user experience
in your app on that platform.
So why should you think
about performance?
The easiest way to sum it up is
that performance is a feature.
It's a core and central element
of giving your users a great
experience in your app.
It's not an extra or a bonus
or something you can get
to at the end if you have time.
It's actually something
that should be
on your mind all the time
while you are writing your app.
There's a couple of
reasons for that.
If your app is really
responsive,
if it always responds to
user input right away,
that actually builds
the trust of your users.
That lets them know that if they
quickly need to access a piece
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
That lets them know that if they
quickly need to access a piece
of information or service
some interaction in your app,
it's not going to
keep them waiting,
and that makes them really happy
and keeps them coming back.
If you are going to adopt
Multitasking on iPad in iOS 9,
not only does your app no longer
have full run of the screen,
it actually doesn't
have full run
of the system's resources
either.
So a performance problem in your
app doesn't just cause a bad
experience in your app anymore;
it can actually cause
a bad experience
in another app as well.
You really want to be known as
a good neighbor in Multitasking.
Apps that are architected to be
efficient with system resources,
like CPU or memory,
don't just feel great
when you are using them; they
actually save battery power
and let your user get
through their day,
and they really appreciate that.
And finally, iOS 9 supports
a huge range of hardware,
and performance is a
prerequisite of continuing
to bring great apps and features
to all of your customers.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to bring great apps and features
to all of your customers.
So hopefully I have convinced
you, you don't have to walk out.
How should you think
about performance?
Well, the very first step
when you are building your
app is choosing technologies.
This is critical because
you need to choose
which technologies are going
to give your users the best
experience in your app.
Once you are a little bit of
the way into building your app,
you can start taking
measurements
and actually understanding
what the user experience is
in the critical interactions
in your app.
Your measurements will inform
you how your app is doing today.
Once you've got those, you
can actually set some goals
for what state you
want your app to be
in before you submit
it to the Store.
And finally, once you've
got all that, you are ready
to start making code changes
to improve the performance
in your app, and there's a
great workflow you can follow
to converge on your targets.
So let's start with
choosing technologies.
Picking the right tool for
the job is a really early
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Picking the right tool for
the job is a really early
and critical aspect
of proactively architecting
your app for great performance.
And the very first step of
choosing technologies is
to know the technologies.
So over the course of this talk,
I'm going to reference several
other talks from this year
and previous years
that cover technologies
that we have found
really helpful
for getting great
performance in apps.
Once you know the universe of
technologies that are available
to you, then you can start
to bring in your knowledge
of how your app works
and what it needs to do,
and pick the best
ones for your app.
A great example here
is if your app needs
to store three strings, it's
probably okay to write those
out to a plist or put
them in user defaults.
On the other hand, if your app
needs to use 3,000 strings,
you might want to
consider Core Data.
Speaking of Core Data, when
you are choosing technologies,
I strongly encourage you
to consider Apple
APIs and frameworks.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
We spent a lot of time trying
to make those great for you
and your users, and we build
our own products on them.
One benefit of adopting
Apple APIs and frameworks is
that after the user
installs your app,
they might install
an iOS update,
and these frequently contain
performance improvements
to core APIs and technologies.
So the next time
they open your app
after installing an iOS
update, it magically got faster.
So you've chosen
some technologies,
and you've started
building your app.
Let's measure a few things.
There's a couple of
categories of performance
that we can think
about measuring.
Let's start with animations.
Animations are what make
your app feel alive and fluid
and let your user know where
they are and what's going on.
The easiest way to
measure the performance
of your animations is the
Core Animation instrument.
Responsiveness is all about how
quickly you react to user input,
and actually, the simplest way
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and actually, the simplest way
of measuring responsiveness
may seem a little bit low tech,
but it's really powerful,
and that is simply
instrumenting your actual code.
I am going to go into
an example of that.
For more complex scenarios that
maybe involve several threads
or lots of system interactions,
there is a great instrument
called System Trace.
And finally, memory.
Memory is the most precious
resource on mobile devices,
and it's really important
to make sure your app is
using exactly as much memory
as it needs and no more.
Again, a simple but powerful way
to understand your
app's memory use --
which I am going to go into
-- is the Xcode debugger.
When you are ready
to dig in more,
there's a great instrument
called Allocations.
And if you think you have
leaks, there's an instrument
to help you track
those down as well.
So let's go into a code
instrumentation example.
Here I've got an IBAction
that's wired to a button,
so when my user taps this,
I am going to load an image
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
so when my user taps this,
I am going to load an image
and put it in my view.
And I want to know
how long this takes.
So I am going to use an API
called 'CF absolute time
get current.'
Now, I don't actually care
about the absolute value
of the current time,
but I do care
about the difference
between these.
This API, although this is
Swift and it's type inference
and it's wonderful, I will
tell you this returns a double,
and specifically, that double
represents the current time
in seconds.
A second is actually
a really long time.
Your user is really going
to feel it if something
in your app takes a second.
So we actually find milliseconds
to be a more actionable
unit of measure here.
So we are going to subtract the
start time from the end time
and multiply the
result by a thousand
to get our measurement
in milliseconds.
It's important to profile
the release configuration
of your app so that you get
all the compiler optimizations
that your users will get and so
that you understand how your
app is actually behaving
in the field; however,
it's equally important not
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
in the field; however,
it's equally important not
to submit your performance
instrumentation
to the App Store.
So my suggestion is that
you actually create a copy
of your release scheme in Xcode
and define one additional define
so that you can build a
release version of your app
with the performance
instrumentation quickly
and easily.
So what types of responsiveness
are we interested in measuring?
Well, definitely taps
and button presses.
Most commonly, you'll be
doing these in IBActions.
You may also do them in
UIView touch handling code.
Or you may have a
gesture recognizer target.
Another aspect of your app's
performance that's interesting
to understand is what it
feels for the user to move
around in your app and
switch to different views,
whether that's using a
tab bar or modal views.
In this case, we find
it interesting to think
about the time between
'view will appear'
and 'view did appear'
because that lets you compare
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
which of your views take longer
to get ready to get on screen.
So you've taken some
measurements,
and you understand how
your app is behaving.
How do you set goals for
where you want your app
to be before you
submit it to the Store?
Animations feel great, they feel
realistic and fluid and alive
when they run at 60
frames per second.
I'm not going to talk too
much about them this year
because there's a great talk
from last year you can check
out called "Advanced Graphics
and Animations in iOS Apps,"
and there they cover the
Core Animation instrument
and how you can use it to
measure the performance
of your animations
and also improve it
across the full range
of hardware.
We are going to spend a lot of
time today on responsiveness.
Responsiveness is
all about, again,
how you react to user input.
And we've found that if
it takes too much longer
than a hundred milliseconds,
your user starts to feel it.
So your goal for any
responsiveness scenario should
be 100 milliseconds.
By the way, you want to think
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
By the way, you want to think
about reaching these
performance goals
on the oldest hardware
you intend to support.
If you are targeting iOS 9, that
might be the original iPad mini,
the iPhone 4s, or
even the iPad 2.
If you've already
got one of those --
or rather, if you've
still got one of those --
please keep it, continue to
use it, continue testing on it.
And if you don't, well, there's
a great refurbished section
on the Apple Online Store.
[Laughter]
So you've set goals.
You've taken measurements.
And now you want to proceed with
making code changes in your app
to improve the performance.
How do we start?
First of all, don't guess.
You really would like to profile
with tools and act on evidence
of what's causing the
performance issues in your app.
It's charming to think that your
intuition will be right all the
time, but it's probably
closer to a coin toss.
Along those lines, avoid
premature optimization.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Along those lines, avoid
premature optimization.
Don't complexify your code
until you have evidence
that the simplest possible
implementation is not sufficient
for great performance.
Frequently, the mechanisms
that people introduce to try
to proactively stave off
performance problems end
up causing performance
problems of their own.
Make one change at a time.
You do want to start to hone and
develop your intuition for how
to approach improving the
performance of your app,
and it's very hard to
understand which thing you did
that actually ended up improving
the performance of your app,
so make one change at a time.
And what I am really
trying to say here is
that there's no magic.
This is just like
ordinary debugging.
So bring the same rigorous,
scientific mindset you would
to debugging an app crash
or a functional issue.
So here is the picture
I want you to print out,
stick on your wall, set as
the wallpaper on your Mac.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
stick on your wall, set as
the wallpaper on your Mac.
This is how we go through
making code changes
to improve performance
in an app.
The first thing you need to
do is reproduce the issue.
Then profile with tools to get
an understanding of which areas
of your code are
actually contributing
to a performance issue.
In a sufficiently
large code base,
your intuition may actually
not be correct here,
so it's good to collect
evidence.
Then, once you've zoomed in on
the specific sections of code
that are causing
less-than-desirable performance
in your app, you can measure
exactly how much time you are
spending there.
And finally, you can make a
single targeted code change
to try and advance
towards your goals.
Often it's the case that one
single change doesn't get you
all the way to where you
want to be, and in fact,
several different
changes will combine
to eventually get
you to your goal.
So that's why this is a
cycle because you may find
that after you change code and
reproduce again, it's better,
but you are not where
you want to be.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
but you are not where
you want to be.
So you can keep going through
the cycle until you are happy.
Profiling and measuring are on
that slide, and maybe they seem
like they are similar,
but actually,
they are two discrete steps
of improving the
performance of your app.
Profiling, again, is using
tools like the Xcode debugger
and Instruments: Time
Profiler to get an overview
of all the aspects of your
code that are contributing
to your performance scenario.
Measuring means instrumenting
a specific area of your code
to understand precisely how
much time the user spends
waiting there.
And again, the 'CF absolute time
get current' example I gave is
actually a really
powerful way to do that.
Again, for more complex
scenarios, there's System Trace.
So let's talk about
responsiveness.
Responsiveness is all about
reacting to user input.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Responsiveness is all about
reacting to user input.
And we can't talk about
responsiveness without talking
about your app's main thread
because your app's
main thread is
where you consume
all the user input.
That's anything from
the touchscreen --
a tap or a scroll -- anything
from the other sensors
on the device, like
an orientation change,
and multitasking resizes and
other system state events.
If your main thread is
mostly dedicated to the task
of responding to user input,
your app will feel
really great all the time.
If you're a little less
careful with what you do
on your main thread or
maybe you do everything
on your main thread,
it's possible your app
will appear hung or frozen.
So what should we avoid
doing on our main thread?
Specific things to watch out
for include CPU-intensive work.
That can mean parsing a long
string that you downloaded
from the network, maybe
applying a filter to an image,
and tasks that depend
on an external resource.
And I will come back to that.
I am not going to spend too much
time on CPU-intensive work today
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
I am not going to spend too much
time on CPU-intensive work today
because there was a great talk
earlier this week from the folks
that made Instruments
called "Profiling in Depth,"
and they actually talk about
improving the performance
of CPU-intensive code in
Instruments using Instruments.
It's awesome.
So let's go back to tasks that
depend on an external resource.
Another name for these
is a blocking call.
It's called that because
it prevents your thread
from making progress,
so you are blocked.
Now, what's a blocking call?
Some of you may be familiar
with the concept of a syscall.
Any code path that ends
up making a syscall is
considered a blocking call.
As I mentioned, these
typically involve resources
that are not currently in
memory, commonly things
like loading things
from the disk
or fetching stuff
over the network.
Occasionally, your main
thread will also get blocked
because it's waiting for a
resource that is available,
but it's waiting for
someone else to finish using
that resource because that
resource only allows one client
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that resource because that
resource only allows one client
at a time.
So, how do you spot blocking
calls in your thread?
Well, sometimes they
jump right out at you.
The word "synchronous" is
a synonym for blocking.
So that's a clue.
When you are reading through
your code, that should light
up and draw your eyes.
So, great, we spotted this
blocking call in my code,
'NSURLConnection send
synchronous request.'
Well, what do I do now?
Well, sometimes there is
an asynchronous API --
especially for APIs
that specifically call
out that they're
synchronous in their name --
that you can easily
switch over to.
In this case, we got
lucky, and in fact,
this one helpfully has the word
"asynchronous" in the name,
so we know exactly what we
are getting ourselves into.
Unfortunately, it's not quite
as simple as search and replace.
You are changing the order
that your code executes in,
and you may have other
code that depends
on the result of this operation.
So unfortunately, some
restructuring is required.
But let's say you don't
have an async equivalent
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
But let's say you don't
have an async equivalent
that you can easily switch to or
you want to move a whole section
of code off the main
thread in one operation.
In that case, use
Grand Central Dispatch.
Grand Central Dispatch
is an Apple technology
that manages a global
thread pool in your app.
It's already there.
Even if you don't notice it.
If you are familiar with
programming with threads
on other platforms, Grand
Central Dispatch sort of takes
out all the confusion
and trouble of worrying
about starting threads and what
state they are in and so on,
and lets you express tasks
that you'd like to run
as closures or blocks.
These closures, once you submit
them to Grand Central Dispatch,
run on an arbitrary
thread in your process.
Arbitrary threads are awesome
because you didn't have to deal
with starting them, and
you don't have to think
about how many there are,
but they have the caveat.
Since you don't control which
thread your code is running on,
any operation you
express in a closure
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
any operation you
express in a closure
or block must be safe
to do on any thread.
What are some examples?
Well, some objects are
actually restricted to access
on the main thread only.
UIKit views and controllers,
for example, must be created,
modified, and destroyed
only on the main thread.
Some objects, like Foundation
and Core Graphics objects,
allow use from any thread.
However, many of these have
the additional stipulation
that the caller is responsible
for ensuring only one thread
accesses them at the time.
They don't protect
themselves internally.
So if you intend to use
them for multiple threads,
you frequently have to implement
the protection yourself,
and the preferred way of doing
this is a serial GCD queue.
The best way to find out
how your objects expect
to be treated is to
read the headers.
Per object, usually
near the initializer,
there should be a description
of exactly how it expects
to be accessed by
threads in your app.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to be accessed by
threads in your app.
So, let's go back
to my example here.
What do I do in this code?
I load some data from a file.
I process and filter an image.
And finally, I put it in image
view in my view hierarchy.
So right now when the user
taps a button in my app,
my main thread looks a
little bit like this.
It does those three things
in order one after another.
Simple, straightforward,
easy to understand, great.
Unfortunately, should the
user happen to try and scroll
or rotate right when I am
in the middle of doing this,
we won't be able to service
that input until later.
And the thing with blocking
calls is you never really know
how long they are going to take.
It's sort of like the weather.
So the user is going to be kept
waiting for some unknown amount
of time, and that
will make them sad.
So how do we fix this?
Well, we can use Grand
Central Dispatch,
and we can use a Grand
Central Dispatch API called
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and we can use a Grand
Central Dispatch API called
'dispatch async.'
Now, 'dispatch async'
takes two arguments.
The first thing you
need to pass in is
which queue you want it to use.
As I mentioned, there's already
several queues in your app
that GCD has created for you.
And I am going to get one
of them using the 'dispatch
get global queue' API.
Because there are several,
I have to actually tell GCD
which one I want,
and here I am going
to use a 'Quality
of Service' class.
'Quality of Service' is how you
tell the system how important
the work you are asking it to do
is relative to both other work
on your app and work across
the rest of the system.
In this case, because
this is the direct result
of a user action and the user is
probably waiting for the result,
I am going to use the
'user-initiated' QOS class.
The final argument to 'dispatch
async' is a closure containing
the code you would
like it to run.
So, great.
I am done.
It's off the main thread.
Right? Not quite.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Right? Not quite.
As I mentioned, UIKit views
and controllers are only safe
for use on the main thread, so I
can't put them in this closure.
So that wasn't really the
particularly slow part
of my code anyway, right?
The first two lines
were the blocking calls.
Why don't I just move this
back out onto the main thread?
Unfortunately, that's not
quite going to work either
because I have actually
changed the order
that this code executes in.
This closure is not
guaranteed to have run
by the time 'dispatch
async' returns.
Hopefully it will run
quite shortly afterwards.
But most likely, once GCD
has fired the work off
to the dispatch queue,
it will immediately move
on to the next line,
and at this point,
my image is still
likely to be nil.
The user will never
see their image.
That will also make them sad.
So how do we fix this?
Well, we can actually make
another call to 'dispatch async,
and this time we'll use a
very special queue called the
main queue.
The main queue is guaranteed to
be serviced by the main thread.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
The main queue is guaranteed to
be serviced by the main thread.
You can get it using the API
dispatch 'get main queue.'
What that means is if you have
objects that require access
on the main thread, you can
still put them in a closure
and pass it to dispatch.
You just have to make sure
they run on this queue.
So I have done that here and now
my imageView is safe and happy.
With this, we've moved that
work off the main thread,
but then when we've
needed to use objects
that must be accessed on the
main thread, we have done
so once the data
is ready for them.
And by the way, the original
problem we were trying to solve,
should the user try
and scroll or rotate,
that will get serviced right
away, and they will be happy.
Thank you.
[Applause]
So what types of blocking
calls are you likely
to find in your code?
They are sneaky and they
hide in all kinds of places.
As I mentioned, networking.
NSURLConnection and friends.
It's very easy to,
without meaning to,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
It's very easy to,
without meaning to,
make a synchronous
call to the network.
Usually you can switch
to asynchronous API,
or if you actually
want even more control
over when your application
accesses the network --
and in some cases even
let it download things
when it's not running --
I encourage you to check out the
NSURLSession background session.
Foundation initializers.
When you come across
these in your code,
they don't look that scary.
It's one line.
How bad can it be?
But actually, some of these,
like ones with the name
'contents of file' or 'contents
of URL,' may end up having to
use the disk or other resources
to service your request.
And finally, Core Data.
Again, they just look like
objects, not bad, right?
Core Data frequently does a
lot of I/O on your behalf.
Luckily, it's pretty
easy to move some
of your heavy recorded
operations
to different concurrency modes.
And actually, there's great
new API in Core Data this year
to simplify that and other
common bulk operations.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to simplify that and other
common bulk operations.
You can go learn about it in
the session from the other day,
"What's New in Core Data."
So if you find a blocking call,
switch to asynchronous
API or use GCD.
If you want to know more
about GCD, including, again,
new API this year that's going
to simplify common operations
and also the quality
of service classes
that I mentioned earlier,
there's a great talk
from just an hour ago
called "Building Responsive
and Efficient Apps with GCD,"
and I encourage you
to go watch it.
Let's move on to memory.
Like I said, memory is
the most precious resource
on mobile devices.
If you are planning to adopt
multitasking in your app, again,
you don't have full run of the
screen anymore, so it stands
to reason that you also don't
have full run of the rest
of the system's resources.
If you are going to bring
some of the code from your app
to watchOS, it's
especially important
that its memory footprint
be compact.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that its memory footprint
be compact.
Once again, iOS 9 supports
a huge range of hardware.
If you want to bring your
great apps and features to some
of the lower-end devices
supported by the OS,
memory is extremely
important on those systems.
And finally, if you are the
developer of an extension,
think about the fact that your
extension may now be called
on to run when there are two
other apps on the screen.
So memory will be in high
demand, and you need to be able
to get away with using
as little as possible.
So let's get a little bit
into how memory works on iOS.
Ground rules.
There is not enough physical
memory on any iOS device
to keep all the suspended
apps in RAM at the same time.
When we come under memory
pressure, we actually have
to evict things to make
room for the foreground app.
On OS X or a PC operating
system, we might write the state
of those apps out to
disk first, but it turns
out that doesn't make
sense on mobile devices,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
out that doesn't make
sense on mobile devices,
so when you get evicted,
you are just gone.
There's lots more detail
here and lots to get into,
and there's actually a great
talk from a few years ago called
"iOS App Performance Memory."
The slide template and colors
are a little different,
but the information is really
solid, so please go watch
that one if you are interested
to learn more about
how this works.
But if you've never thought
about memory in your app before,
it boils down to this --
reclaiming memory takes time.
If you've already used up
all the available memory
in the system and then
you need some more,
you might be kept waiting
while the system evicts things
on your behalf.
If you suddenly request
a large amount of memory,
the system might need to
evict several different things
to service your request,
and that can influence the
responsiveness of your app.
Conversely, when you
are in the background,
if your footprint
is very compact,
it's actually less likely
that you will be one
of the things that gets evicted.
And so when the user
then returns to your app,
you will be able to resume
instead of launching,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
you will be able to resume
instead of launching,
and that will feel a lot faster.
So if you've never thought
about memory before,
it's really important
as a first step
to rationalize your
app's memory footprint.
And what that means is
to think about the types
of resources that it uses.
These might be strings; maybe
long blobs of JSON or XML
that you downloaded from the
network; images that, again,
came from the network or the
user took them with the camera;
and again, Core Data managed
objects, which use a lot
of underlying resources to
sort of make the magic happen.
Once you've thought about
these resources, you can start
to group them according to
which user interactions depend
on each kind, and that helps
you establish a mental model
of the resources
that your app uses.
Once you've done that,
we can quickly check our work
using the Xcode debugger.
To get into more detail, we
would reach for the Allocations
and Leaks Instruments.
I am not going to go into
those today, but please check
out this talk from last year
called "Improving Your App
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
out this talk from last year
called "Improving Your App
with Instruments" to
get started with those.
So let's go back to
the Xcode debugger.
I have downloaded the
Photos framework example app
from the Apple Developer
website.
I've plugged in my phone,
opened the Xcode project,
and hit build and run.
And now I am just going
to look at the top left
of my Xcode window
at the debugger.
Zoom in on that.
So, here, right away I can see
without touching my phone the
first number that's interesting
to me.
Now I know that right
after my app launches,
before the user does anything,
I am using about 10
megabytes of memory.
The next data point that I want
to collect is that I want to go
and do whatever the most common
user interaction in my app is,
so because this is a Photos
app, I am going to open a photo.
And now I've learned that to
open a photo, my app needs
about another 2.5
megabytes of memory.
At this point, an additional
interesting experiment is
to repeat the same
action over and over.
So I might open the
same photo or a couple
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So I might open the
same photo or a couple
of different photos a few times.
If my memory footprint
continues to grow,
I may have a memory issue
worth investigating.
The final point of
interest here is I am going
to use the Home button on my
device to suspend the app,
and I am going to
see what happens
when it goes into
the background.
And it looks like it shrank down
to actually a little bit smaller
than it was right
after it launched.
This is a great balance
to strike.
You don't want to repeat work
that you did during launch
on resume, but you also want to
stay compact in the background
to make sure your users actually
get to experience that resume.
Important to note that the
Photos framework example app
didn't actually have to do
anything special or magical
to achieve that behavior.
It's actually just a
really straightforward
and minimal implementation
of Apple technologies,
and Apple technologies actually
have this behavior often built
in, and they manage sort of
their underlying resources
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
in response to application
lifecycle events automatically.
So you don't need
to worry about it.
However, if you have large
objects or other resources
of your own that you'd like
to sort of dynamically lose
and get back in response to
application lifecycle events,
the easiest way to do
it is to use NSCache.
In some cases, though,
you might have things
that can't be neatly
represented as evictable objects
for NSCache, and then you have
to actually implement
custom code that responds
to system lifecycle
notifications in your app.
A few that you might
be interested
in are the 'did enter
background' notification.
Your app will get this
when it's suspended,
and this is what NSCache
uses to actually shrink
when you go into the background.
Another interesting one is the
memory warning notification.
The system actually sends
this before it starts evicting
suspended apps to give
them a chance to shrink,
and maybe they can
avoid getting evicted
if their footprint goes down.
So here is a quick
example of that.
I am going to use the
default NSNotificationCenter.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
I am going to use the
default NSNotificationCenter.
I am going to add an
observer for, in this case,
the 'did receive memory
warning' notification.
And all I am going to do
is call some 'custom cache
purging' code.
Maybe this walks a
linked use of C structures
and frees some other memory.
An important note,
if you do register
for NSNotificationCenter
observer, especially in init,
please be sure to remove
yourself on deinit.
So memory is actually
so important
that there's another
talk I am going
to encourage you to go watch.
It's called "Optimizing Your App
for Multitasking
on iPad in iOS 9."
But actually, even if your
app doesn't run on iPad,
or you have no plans to
support Multitasking,
please go watch this talk.
They go into a lot of detail
about the types of resources
that applications use,
the patterns they
typically access them in,
and more information about
how to have your app respond
to system memory state.
It's really great.
Last but not least, I hope
you are all really excited
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Last but not least, I hope
you are all really excited
to bring native code
to watchOS 2.
When you are thinking about how
to build your watchOS 2 app,
you have to start
with a great design,
a design that really focuses
on the essential functions
of your app and makes
them easy, delightful,
and accessible to the user.
If you need help with that,
there's a great session
you can go watch called
"Designing for Apple Watch."
Once you've got a great design
for your Apple Watch app,
then you can start to
think about which aspects
of your iOS app it might
make sense to reuse.
This could include actual code
or familiar access patterns
to APIs and frameworks that are
shared between the platforms.
Sometimes something
you are doing
on iOS might actually
not make sense on watchOS
for performance reasons.
And you will end up
implementing new mechanisms
to achieve the same result
on the other platform.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to achieve the same result
on the other platform.
watchOS users expect
short, simple interactions,
and they expect to always
see recent and relevant data
in Apps, Notifications,
and Glances.
What does this mean for
you as an app developer?
The most likely thing
the user will do
on watchOS is just
launch the app and look
at the one thing it shows
immediately afterwards.
So what are some things we can
do to get great launch time
and great responsiveness
on watchOS?
Focus on minimizing
both the amount
of network traffic you generate
and the amount of work you have
to do on the device to do
something sensible with it.
If you are accessing a
server that you can control
and add new APIs to, make sure
you are sending appropriately
sized and formatted
responses down to the Watch.
This can include something as
simple as removing unused keys
from JSON or XML
blobs; resizing images
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
from JSON or XML
blobs; resizing images
so that the Watch can
just display them exactly
as they came over the
wire and doesn't have
to do any extra work; and
if your API is accustomed
to feeding devices with large
screens that can show 10
or 20 records at a time,
it may be sending back all
that information in one call.
But actually, for the Watch,
you should send only
the appropriate number
of records needed to
display a single screen.
To show fresh, relevant
information all the time,
it's important to
use your iPhone app
to keep the app context updated.
The app context is a piece
of bidirectional shared state
between the platforms.
So when the user takes
an action on either end
that will cause them to expect
to see something different
on the other end,
that can be updated.
The API for doing this is
'watch connectivity update
application context.'
A great time to do this is
when your iPhone app gets woken
up by background app refresh.
When it's done downloading
new information
and updating its own
snapshot, it can also push
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and updating its own
snapshot, it can also push
that information
over to the Watch
so that it will be ready
next time the user launches.
Finally, in case you're
relying on a server
that you can't change
for some reason,
let's say you're hitting
a third-party API,
you can use the power of your
iPhone's network connection
and CPU to actually implement
an intermediary that formats
and sizes responses
for the Watch.
The API you would use to do
this is 'watch connectivity
send message.'
So you would send a
message to the iPhone,
requesting whatever you need,
and the iPhone would
download it,
and do all the operations I
mentioned, remove unused keys,
reduce the number of
records, resize images.
Then it could send a compact and
actionable reply to the Watch,
again, over WC Session.
So wrapping up, performance
is a feature.
It's an essential aspect
of giving your users a great
experience in your apps.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
of giving your users a great
experience in your apps.
And it should be on
your mind from day one
when you are building your apps.
Efficient apps feel great
when you are using them,
they build your users' trust,
and they save battery power.
Please go learn about all the
Apple technologies I mentioned,
and when you are thinking
about building your app,
choose the best ones.
Keep your main thread
always ready for user input.
Understand when and why
your app uses memory.
And to get a great
experience on watchOS, download
and process a minimal
set of information.
Here's some great written
documentation you can get
into if you are starting to
get interested in this stuff.
And again, here are the
sessions that I mentioned.
The first few are
about technologies
that we covered this
year, and there's a few
from previous years as well.
Thanks, and have a great Friday.
[Applause]