Transcript
>> Daniel Delwood: Howdy.
I'm Daniel Delwood, engineer on the Performance Tools Team.
And I'm happy to introduce you to
Advanced Memory Analysis with Instruments.
So let's get started.
Well, what are we talking about today?
Memory. And memory is critical to performance.
It's an extremely important part of your program's
execution and that's because it's such a limited resource.
On Mac if you use too much memory, you'll
start paging and really slow your app down.
And iPhone -- this is even more important because
there's less memory and your app will get terminated.
So today we're going to talk about Instruments.
And Instruments offers you a lot of tools
for tracking down those common memory issues
and is really useful -- what exactly for?
Well, first of all, it's useful to understand your
app's memory usage and this is really a key point
because with understanding comes the ability to
change and the ability to change your application
to do what you want it to do, not what you
think it's doing and maybe isn't already.
So use Instruments to reduce wasted memory.
We'll have a couple demos of this today.
Diagnose memory-related crashes -- a demo of this as well.
And I really want to encourage you from the start to
be proactive about your memory usage: profile early,
profile often, and don't just wait
until you get those terminations
or your app doesn't fare well in a multitasking environment.
So exactly what are we going to talk about?
Well, 5 issues: first of all, eliminating leaks, which
is an issue of wasted memory; abandoned memory --
also wasted memory; messages to the allocated
objects; responding to memory warnings; and finally,
a little blurb on how to use autorelease
properly and track that down in Instruments.
So let's get started.
What constitutes a leak?
Well, leaks are allocated memory
that can no longer be reached.
This means that they're no longer useful to
your program, you allocated them some time,
and now there are no more pointers to it.
So what can you do about it?
Well, you can program more carefully but it's hard to
find these sometimes and that's why we provide tools.
So for example to make this concrete,
say you have an object.
My object and it has an instance variable name.
Well, in your init method, you might say something
like name = [[NSString alloc] initWithFormat:...].
So that creates a string with a reference count of 1.
And then later on, after your object's
been used and it's about to go away,
you'll be running your object's dealloc method.
Well, what happens when you forget
to write the [name release]?
Well, your object still goes away and
that pointer to your string object,
but your string object doesn't go away
because it still has a rough count of 1.
So all right, what do we do about this?
Well, that's what the Leaks template is for.
And Leaks, as its name suggests,
identifies leaked memory on your behalf.
It does this by doing a conservative memory analysis,
scans your process's address space, and it looks for blocks
in your heap that are no longer referenced.
Now, when I say "conservative," I mean that it works
really, really, really hard to make sure that it's reliable.
So that means it may miss a few leaks but it is
very reliable in the leaks that it does report.
So what I mean by all of this is that if you have a bunch of
heap objects, what it does is it scans through your stack,
looking for pointers to those heap objects, and marks
those and also through some global data so this is statics
that you may declare and then scans to other heap objects.
From those it continually, recursively scans and anything
left over that isn't reachable, well, those are your leaks.
Well, great.
Now you know what the leaks are;
how can you go about fixing them?
Well, that's what Allocations instrument is for.
And this is sort of the heavy lifter of our memory tools
in that it tracks all malloc-type heap allocations.
So this includes C's, malloc, calloc, and any other
ways - objective-C, when you say alloc or new,
C++'s new operators -- all of those
things get tracked by Allocations.
And then it provides you those malloc events,
those free events at the end, and if you specify,
you can also get the retain/release and the
autorelease event from the middle and those will come
in really useful as we'll see in some of the demos.
And the other thing, by tracking all of
these allocations, you can type statistics.
So how many strings have you created?
How many of your object controller have you created?
And you can find out when they're deallocated
and that sort of useful information.
And finally, the most important part is that it
gathers backtraces for each one of these events.
And so with that backtrace, you can put it
in the call tree and find out what parts
of your code are allocating the most memory --
those probably are what you want to focus on.
One final note, the Allocations instrument does incur
a little bit of overhead with all this tracking.
So it's perhaps not the best idea to use Time
Profiler or Time Analysis at the same time.
All right, so let's go ahead and show
a demo of finding and fixing leaks.
All right, I'm going to launch my project and for
the purposes of this, I'm going to be showing most
of this technology in the iPhone simulator, although
all of this applies to Mac, iPhone, iPad as well.
So here we go.
I'll go ahead and launch my application.
It's a really simple iPhone app.
All it does is it displays a bunch of
what we want to call "breadcrumb entries."
And so these are places you've been, maybe
you took a picture and left a description.
But you can just go back and forth and
there's even a latitude and a longitude.
So this is geocoded.
The important part is: is this application leaking memory?
Well, we can find that out directly from Xcode by
running from this run menu, run with performance tool,
and we'll just select the leaks template.
So Instruments starts up in the
background and starts recording.
We'll deal with that later.
All right.
So it starts recording in the background
and I can start using my app.
But the first thing you'll note is that there's
some graphs in Instruments being displayed already.
So this is the immediate mode where you
can see your data as it's being recorded.
So if I zoom in on the track view, you'll
notice that Allocations right now is showing me
where I've allocated the current memory in my application.
So at the beginning there was a spike -- is it launched?
And then there was another spike,
probably as I loaded in my SQLite database.
And if I wanted to change the view, I could even
change in the Style option in the inspector.
But for now that's good.
And leaks -- as I already discovered something --
and so its graph is showing me the number
of leaks discovered in the total leak bytes.
So if I want to find out some more information
and go ahead and click on the Leaks instrument.
And let me scroll a little bit more
just so I can find a few more leaks.
But in the table view here, what we have is
a bunch of what looks like leaked strings.
So there's 13 leaked strings and 2 leaked strings.
And this first one says there's 600 bytes leaked --
well, not too much but we should
really endeavor to fix everything.
And if I turn this down, you'll see that what this is
doing is this is taking 13 different leaks of strings
and aggregating them into one place because
usually code that leaks, leaks multiple times.
And so by doing this, you can find that fixing one of these
strings will fix all 13 -- in this case, possibly even 15.
So I'm interested in this object.
What I'm going to do next is take a look at the backtrace.
So at the top we have that view that I can
bring in and it's the extended detail view.
And you may recognize this form of stack trace from the
Xcode 4 with the backtrace compression slider at the bottom.
So immediately I can see that my code in black is calling
NSString initWithFormat from my root view controller.
And I can just double-click to
jump there, see it my source view.
And I'm going to open it in Xcode just
to make it a little bit easier to see.
And so what we have here is that string, that subtitle
text, the latitude and longitude that you see in the entry.
And since most leaks are pretty simple, I can just go ahead
and look around this method and
make sure I did things right.
So I have an alloc initWithFormat -- that gives me a
reference count of 1; there's no autorelease at the end.
So I need to release this somewhere and
if I follow it through in the function --
oh, there we go, we did release it properly.
So it looks like this isn't going
to be a really simple leak.
So going back to Instruments, the biggest thing to
understand here is that leaks aren't always that simple
and there aren't just the allocation point --
you need to know the full story and
that's why we record the reference counts.
So I'll just go ahead and select any one of these
leaks and press the Focus button next to it.
And it gives me the reference count list so I can
see that there's a malloc, a retain, and a release,
and the RIF count dropped
to 1 but what we really want is for it to drop
to zero before we have no references to it.
OK. So we looked at the malloc, let's look at the retain.
So just jumping here we can see that this is
in my breadcrumb cell set location string
and that's displaying on the screen.
And what we've got is a string coming in, we're copying it,
which makes sense, and we're also doing some special work.
This is probably why we didn't synthesize the setter.
And I'll even jump to the header just so we can
make sure that we specified it as Copy and we did.
And great, that looks probably right.
And we go back to the history and look at the release.
And this again is in that table view cellForRowAtIndexPath
so that was the release we saw in the first function.
So obviously it's this retain that's
unbalanced and that was in the cell.
Well, perhaps we're leaking the cell, too.
Or perhaps the cells aren't going away as we expect.
So if we go back to the Allocations instrument,
the view here is showing you the statistic.
So like I was saying, all the -- telling
you how many numbers you've created
and giving you the number of bytes there.
And what we want to know is: for our
breadcrumb cell, how many are still alive?
Well, only 7.
Well, that makes sense.
We have ourselves an iPhone app and so
we're probably doing cell reuse properly.
Oh, so that's interesting.
That means that our set location
string really is leaking somewhere.
Well, let's take a look at it one more
time, now that we know it's at fault.
So we are setting an end location string
and we're even releasing in dealloc.
But the one thing we are forgetting is:
if we call this over and over again,
we're going to be leaking the instance variable --
while we properly copy the string,
we're not releasing the location string.
And so it's a very simple fix, we can say
NLocation string release just above it.
But that's not quite right.
What if MLocation string and string
are the exact same object?
Well, following the memory management
guidelines, we really should check here
and say if MLocation string is
not equal to string, then do this.
So all right.
Should we build?
Go ahead and just Stop and Rerun.
And I'll use the app again.
And it doesn't look like we're finding any leaks.
So there we go, that's just one leak fixed.
Again, it's important to make sure that you
look at the whole life cycle of the leak.
[ Applause ]
All right, so again, just want to reiterate this
is a very, very common thing -- common mistake.
It's just looking at the allocation point.
And the reason you can't do it is
because it's not the whole story.
You need to know what the object was assigned
to, what instance variables, where it went.
And it's also because framework-created objects can actually
be leaked by your app code because if you get an object back
from the framework and you stuff it away in an instance
variable and, say, forget to release it in the dealloc,
you're responsible for leaking the object.
Finally, just want to reiterate: focus
on a single instance to investigate.
Use that little Focus button and just look at one instance.
Finally, memory management guide: it's a really
useful document, there's a lot to it and, you know,
I still learn great things from this thing.
So that was eliminating leaks and
that's one type of wasted memory.
Another type of wasted memory -- it's even
harder to track down -- is abandoned memory.
And we find that it's actually more
times even more important than leaks.
So what is it exactly?
How do we define it?
Well, leaked memory was memory that is
allocated but can no longer be reached.
So that means it's inaccessible and the tools --
just look for any blocks with no more pointers.
Now, abandoned memory is a little bit different from that;
it's accessible allocated memory that is never used again.
It's conceptually abandoned by you, the
programmer, abandoned by your app; it's leftover
and it's wasted or perhaps just forgotten.
And so this memory is not nearly as easy to detect and
more importantly, it also occurs in garbage-collected code.
So you really have to be careful about
abandoned memory and it's a big problem.
So let me give you a concrete example
of what I mean by that.
How can you abandon memory?
Well, one way is extraneous information --
information that your app doesn't really need.
So for example, let's say you have a tic-tack-toe
game and you want to support one little feature:
undo or redo the very last move, that's it.
Well, you could implement it pretty simply
by saying, "Whenever a user makes a move,
add that current game state before they
make the move to the previous game states."
And then when someone selects a new game, well, you'll add a
blank state because you don't want to undo pass a new game.
And then you'll probably have a button action that
says, "Undo or redo whatever the last move was."
And so that just looks at the last
game state and switches it.
So where is the abandoned memory here?
Well, the problem is that we've
got a previous game state's array
and every single time the user makes
a move, we add more states to it.
Well, only the last one's useful for
this simple undo and redo feature.
And all of the rest of it is abandoned and will just grow
without limit as they continue to use your application.
Another common example is a faulty cache.
Now we've seen this many times and caching itself is great.
Caching makes your app more responsive, makes it
faster, but a faulty cache is what I'm talking about
and that's a cache that doesn't work as designed.
So take this example: it's just a quick little function
to look for an image in a directory with an index.
So if you've got 10 images, maybe
you're looking for image 3, right?
So it looks in the image cache first and says, "Hey,
is there an object in there with
this directory URL and this index?"
And then if there's no index, it creates
it alloc init autorelease, great.
And it sets the image in the cache for that
stream key, using the URL and the index.
Well, let's pay really close attention
to this example here because the key
that we looked up was using stringWithFormat%@.
And that calls description on URL and gets the
key we wanted, the URL followed by the index.
However, the key that we set the
image as was actually using the %d.
So this is just a typo.
It's an easy mistake to make, but in this case
it's a really hard one to find because it looks
like your cache is working properly but every
time it's actually allocating a new image
and this is definitely not what you want.
OK. So if that's what abandoned
memory is, how do we detect it?
Well, there's a basic principle and
that's: memory shouldn't grow without bound
when repeating operation that returns
the user to the same state.
So conceptually this makes sense to programmers.
If you say, "Push and pop a view controller,"
you'd expect the memory goes back to where it was.
Or say maybe you open and close a window, right?
The idea is that you think your application --
before using the window and after using
the window -- will be the same thing.
Or maybe it's even a different, more subtle action
such as just toggling an app preference
on and off using a check box.
Maybe it's setting user defaults behind the scenes.
The point is, you'd expect it to go back where it was.
Now this is where you would need to use
Instruments to verify your expectation
and making sure that you're not abandoning any memory.
So there's three steps to it.
Three steps that you as a programmer need to
do: one, get your app into a starting state.
So let's say you're doing that pushing
and popping of view controller,
you launch your app and get it to the first view.
Then you need to perform an action and return
back to that state, that's your user scenario --
that's what you're going to repeat over and
over again because then you take a snapshot
of the heap using a new feature
in the Allocations instrument.
And as I said, it's really important
that you repeat steps 2 and 3.
You need to perform that scenario over
and over again and keep taking a snapshot.
Because the first time there may be correct caching or
maybe you're warming up something in your application.
And so it's that over and over again.
It shouldn't increase without bounds
as you repeat that action.
So let me go ahead and show you a demo
and help make this make more sense.
So our application had no leaks as last time
we checked it and this time I'm going to say,
"run with performance tool allocations," to use that
new feature as part of the Allocations instrument.
So it comes up and it's showing me
the statistics in the table view here.
And so I've gotten my application
to a starting state, great.
Well, now what I want to do is I want to take a snapshot
and that's what this new heap-shot analysis
category with the Mark Heap button does.
And so when I press it, you'll notice in the Track
View a little flag appears and in my Detail View,
the view has been updated to show me the
heap-shots, that's what we call these snapshots.
And the one I just took was the baseline snapshot.
And so it shows me there's about 3 megabytess
of growth since the beginning of the application
and then there's 21,000 objects still live.
OK, great.
Now I need to repeat my scenario.
So what I'm going to do is I'm just going
to click on the first one and go back.
And you can even see there's a bump
in the Track View as I did that,
probably as some caches were warmed or something like that.
So now all I'm going to do is press
the Mark Heap button again.
So I'll do it a second time and even a third time.
And so what we notice here in the Detail View is that the
first shot I took, there was a heap growth of 167 kilobytes
with 2100 objects that were still
living now from that snapshot.
And then the other two were growing by about 400 objects.
Well, is that going to continue forever?
If I just click back and forth, is my app
going to grow by 400 objects every single time?
It sort of looks like it.
So I've done it 6 times and now I'm done.
Oh, but wait, I actually wanted to take a snapshot at the
end there because I forgot to before I stopped the app.
Well, what's was very nice about this new
feature is you can do analysis after the fact.
So a few outtakes of the old screen or maybe you
have an old trace document that you want to analyze.
All you do is go to the Track View, drag the inspection
head to where you want the snapshot, and press Mark Heap.
And it drops another snapshot point there.
[ Applause ]
OK, so now our problem is that it's
going up by about 380 objects every time.
How do we fix this?
Well, what we're going to do is we're going to find
one of those that seems to be most representative
of these snapshots, say this one -- 366 objects.
So I'm going to use the Focus button, just focus in on that.
And what I see is all those objects
that were created in that time range
that are still living at the end of the program's execution.
So these are the objects I'm really, really interested
in because the fourth time I would expect none
to be alive at the end of the program's execution.
So I'm going to look for objects
that are probably created by me
and the first thing I see is that
there's a breadcrumb entry.
My app is called Breadcrumbs.
Each one of those rows represents
an entry and there's one of those.
And that's probably holding onto some
instance variables and some other stuff.
Is there anything else I recognize?
Oh, yes, a composed view controller; I wrote that.
Whoa, there's one view controller still live
after every iteration but I didn't link it.
Well, to figure this out, what I need to do is just turn
this down and see the address of the view controller
and I can bring in the Extended Detail
View and show me where it's allocating.
Now I go ahead and jump to that and I'll
open an Xcode here so you can see it.
This is my "did select row at index" path method.
So user taps on the row and what we do is we say,
"OK what's the entry that that row represents?"
Once we have that, it looks like we're
actually caching view controllers --
don't necessarily know why we need to
cache before each entry but it looks
like this is performance enhancement
we made to the application.
Now the interesting thing here is that we notice that
our snapshot was creating the object every single time.
Now, why? Because I clicked on the first row
but there's the same entry every single time.
So why is that?
Well, it's probably a faulty cache on our fault and
so what we look is we see that the cache is looking
up for the key entry, which is a
breadcrumb entry type, it's an object.
And that we're setting it for the entry as well.
So that looks like these two match, that's good.
What about the entry?
Is it performing properly?
Well, to use it as a key in NSDictionary, the entry has to
actually conform to NSCopying and that's because by default,
NSMutableDictionaries will copy in their keys.
The other things we need to make
sure that we wrote correctly --
so that there's copy with zone to conform to NSCopying --
the other things we need to write are "hash" and "is equal"
because the dictionary needs to
know how to hash it and how to check
if two different keys -- two different objects -- are equal.
But we've clearly written hash and it's just the row ID
because what these breadcrumb entries actually
represent is an object from a SQLite database
so that's the primary key, that's all we need.
But there isn't an "is equal."
So the problem here is just sort of
like comparing two cans of soup, right?
If they're both tomato soup, well, they're different
cans but we haven't actually taught our cans how
to compare to each other to read the labels.
And so what we need to do is write an "is equal" method.
So I will go ahead.
I've got one prewritten here.
There we go.
And I'll just drop it in.
And it's a really simple little "is equal."
It says if the other object is the same
class, then we just compare the row IDs.
Great. So simple fix and let's see if
it actually fixing our abandoned memory.
So run with performance tool Allocations.
Here we go, back in the same document.
And I'll wait until it gets all ready
and take a snapshot, a baseline snapshot.
Let me see here.
Oops. And then I'm going to repeat
my scenario over and over again.
So do it the first time, do it the second
time, and you'll notice that, you know,
our change hasn't made much difference on the
first time -- it's still about 2100 objects.
The second time seems to be a little less, about 119.
Well, let's keep doing it and see if there's any change.
So the third time, the fourth.
And one thing we'll notice here in a
minute is that the number of objects
in those previous snapshots is actually
going down from the original numbers.
So I'll talk about that in a little bit.
But the main thing we want to notice here is that for
snapshots 4, 5, 6, and 7, there was no heap growth,
none whatsoever when we pushed the view
controller and when we popped the view controller.
So now we've verified that our application behaves as
we expected and that our cache is indeed working now.
So that is the demo.
[ Applause ]
So I want to talk a little bit more about
the details of how those snapshots work.
But the first thing, if you remember anything about
abandoned memory, is that it will require some work
on the part of the programmer: you need to follow
the steps, you need to have a user scenario,
and you need to verify that your
application's memory growth isn't going
up over time just because you keep stashing away memory.
And very simple things, such as the typo
I showed you can contribute to that.
Now those heapshots.
Well, they're not snapshots in the
classical sense of being immutable.
So let's say for instance that your application
starts up and you get it to that starting state.
Well, once you get to the starting
state, you take a baseline snapshot.
Now, that doesn't mean that all of those objects in the
snapshot will still live to the end of your program.
And that's what you're interested in.
So that when you actually repeat your scenario for the
first time and take a snapshot, perhaps there's some caching
and you see 4 objects, this is sort
of like what we saw in the demo.
If you repeat it again, take a snapshot,
maybe there's two the second time.
But again, those objects can go away
later and that is what you're looking for.
And so as you repeat it over and over again, hopefully
you've coded well enough that those objects will go away
and that the number in between those two snapshots --
the number of objects live at the of your program --
will drop to zero and that is when you actually fix
the problem of abandoned memory for that scenario.
Perhaps it's time to look at a different scenario.
All right.
That's enough for me.
I'd like to pass this off to my colleague, Victor Hernandez.
[ Applause ]
>> Victor Hernandez: So thank you, Daniel.
My name is Victor Hernandez.
I'm also an engineer on the Performance Tools Team.
So the next memory issue we're going to be
discussing is messages to deallocated objects.
And unlike the issues that Daniel talked
about, this one can actually cause a crash.
You probably have seen plenty of crash
reporter logs; here's yet another one.
But what makes this one unique?
Well, if we look at the crashing thread,
you'll see that it's dying in objc_msgSend.
These can be really hard crashes to debug because
you don't know what the object was supposed
to be at the time of the crash.
So how can you get yourself into this situation?
Well, it could be because of an overreleased object.
In this example, you have an NSString that's
being allocated and it gets a rough count of 1.
If at some point in the future, you release that string,
its rough count goes to 0, causing it to be deallocated.
And then later on, if you pass a message to that
deallocated object, well, there's your crash.
So how can you go about debugging this?
Well, Instruments provides you with a very
useful thing called the Zombies template.
And what happens here is a little bit different: you
allocate your string, you have a rough count of 1;
then you do the release and the rough count goes to 0.
But instead of deallocating the object,
you instead turn it into a zombie.
And this is really useful because at the later point when
you actually pass the string by appending format message
to that string, you're actually passing it to the zombie,
which Instruments records and it actually notifies you
with this really, really great dialogue that
comes up and you're one click away from finding
out what all the information you
need to actually fix this crash.
So there's no better way to show you
how this works than with an actual demo.
So I'm going to launch Xcode and
I'm going to bring up my program.
It's right here.
And -- all right.
So I'm going to quickly launch this application.
So the demo application is a simple reader, it consists
of a library of books, each book has an entry with a title
and its author and you can go ahead and read the book.
My users have been reporting that
there's been intermittent crashes.
Let me see if I can actually reproduce that.
Sure enough, there's the crash.
And if I look at the debugger, it's
actually dying in objc_msgSend.
It is my crash, great.
So but I'm actually going to look at this and find out
if I can get much more information using Instruments.
So let's launch Instruments.
There's a whole variety of templates for me to choose from.
I'm going to be running this in iPhone simulator.
And this is the Zombies template that I'm interested in.
So what's interesting here is that the first
thing you'll notice is that it looks just
like I have the Allocations instrument and
there's no zombies, that's the thing about it.
Well, in fact, that's actually not the case.
If we look at the inspector, you'll notice that there's two
extra settings: set one, enable zombie detection turned on,
that's great; the other one is right
here, which is record reference counts.
So in this case, we'll not only get the memory history
that consists of the allocation and the deallocation
of the object, but we'll also get all of the
retain releases and autoreleases in between.
And that's exactly all the extra information that
we really need to be able to debug this well.
So let's go ahead and launch our program.
OK. Let's see if we can reproduce the crash.
Scrolling.
Ah ha, there it is, great.
So Instruments tells us that a zombie has been messaged --
an Objective-C message was sent to your
deallocated object at this address.
And here's the one click away.
You just go to the Focus button and you press that
and down below you get the complete history
-- the memory history -- for this object.
The first thing to note is that we now
know actually the type of the object;
it's a string and it has 13 memory events.
The first one is its allocation, followed by a series of
autorelease retains and releases that pump the rough count
to positive numbers, eventually getting to the point where
it gets a rough count of zero, which in this case turns it
into a zombie and then any later memory event or
message sent to this object will actually be reported
as a zombie event -- that's really useful.
So the place to start with debugging this is to actually
look at the last zombie event and see what it tells us.
So I'm going to bring up the extended detail view.
And it looks like it's happening on this line.
So I'm going to go over here.
And just to see this better, let's bring this up in Xcode.
OK. OK, so what's going on in this code?
OK, it's a method, it's our table view cell selector
and so every time that we populate one of the cells
in our application where this gets called.
But what's so special about it -- oh, right, I added this
special code so that when you're displaying the author,
it actually checks to see if it has a last
name because we don't want to ship any books
that the authors are forgetting their last name.
So I'm putting this really ugly string, "X X incomplete
author names" so that QA catches this before we ship.
Well, this sounds like a good feature,
but unfortunately it's causing a crash.
Well, why is it causing a crash?
Well, it looks like the author variable is
being passed the stringByAppendingFormat message
and this is the zombie right here.
So let's go back and find out where we got the author's
variable from -- it's the author field of the book class.
Well, let's jump to that definition.
Uh-oh. Ah, there we go.
And I want to go to the header because
I'm interested in the declaration.
Sure, it's a string -- we already knew that -- but
looking down below, there's more important information,
it's actually a property and it's been
declared as nonatomic and retained.
That means that every time that you set that property, its
old value gets released and the new value gets retained.
That might be useful information.
OK. Now that we know where we got this
value from, let's go back to Instruments.
And let's go back to the history and see
how we should continue debugging this.
So we got to ask ourselves a few questions:
first of all, what could be going wrong?
Well, the first thing I could think
of is this is an overreleased object.
And if this is an overreleased object, the message might
actually -- that it's crashing on -- should be released.
That's not the case.
So the next thing to think about is whether
or not we're correctly protecting that object
from being released by another reference to it.
And the best way to find that out is to go through all the
releases and see if any of them point to anything wrong.
So I'm going to go to this release,
I'm going to see where it's happening.
Oh, that's not that interesting.
It's just simply the synthesized setter for the author,
although we do know already something about that.
And then -- uh-oh, this is actually
happening on the previous line.
Oh, right, here's the book.
Whenever we set the author property, it
ends up releasing its previous value,
which is what the author variable's pointing to.
So sure enough, this is the bug.
So let me go ahead and fix that.
I should protect my variable.
So one way of fixing this would be to
retain it before and after its use.
If I could type.
There we go.
But in fact, actually, this isn't even necessary because
we know that the book.author is going to be set right here
so why don't we just get rid of book.author.nil --
equal nil -- which is redundant and do all of this?
Save that and try running it again.
I'm going to build and I'm going to try running
it again and see if I get a zombie again.
So I'm going to follow my same steps.
Oh and sure enough -- look, I can scroll all the
way to the bottom and the crash has gone away.
[ Applause ]
So what have you seen there?
You've seen that the Zombie template
instrument is a great tool for being able
to debug crashes associated with deallocated objects.
I want to point out a particular quote from the
reference guide that Daniel referred to earlier,
the "Memory Management Programming Guide" because
it talks specifically about this scenario:
"A received object is normally guaranteed to
remain valid within the method it was received
in (exceptions include multithreaded applications
and in some Distributed Objects situations although
you must also take care if you modify an object
from which you received another
object)," exactly the crash that I hit.
We modified the book and that ended up
modifying the author string that we got
from it; we need to be really careful about this.
So what else do you need to know about the Zombies template?
Well, first of all, it causes a lot of memory growth
because those objects never get
deallocated, they just stay around as zombies.
So you need to use the iPhone and iPad simulator to debug
this because the memory constraints on the other devices.
Also, it's not suitable to use with leaks
because everything will show up as a leak.
And finally, really important take-home message
for this kind of crash: it's not always the fault
of that last objc_msgSend that's causing the crash, it
could be any of the releases beforehand and you need
to know its full memory history to
find out how to debug this crash.
OK, moving along.
The next issue we're going to be talking
about is responding to memory warnings.
Memory warnings are a simple fact of life on iPhone OS.
When the system needs memory, notifications
go out to all the applications.
This is even more common now that there's multitasking, so
there's going to be more contention for the same memory.
Your application needs to respond or be terminated.
And there's a variety of ways of writing the
codes to handle all these memory notifications.
Here's two examples.
But one of the things that can be
really tricky to find out is, well,
what are you supposed to actually do during that function?
Well, it's really simple: to decide what memory
to free, you just simply need to know what pages
in your virtual memory are resonant and dirty.
And Instruments helps you identify these pages.
So first, let's be explicit about this: what
are we talking about with resident dirty pages?
Your application, it's going to
display and edit this Tokyo photograph.
It maps it in initially and the protection is
read/write and it's been loaded copy-on-write.
So if at any point you read it,
that makes the memory resident.
And then at a later point, when you decide to write it,
the page that you wrote has now become
dirty; that's resident dirty pages.
So how do you find out what pages are resident and dirty?
Well, you watch your virtual memory
using the VM Tracker instrument.
The VM Tracker instrument takes
snapshots of your virtual memory.
You can think of it as a visual
depiction of the vmmap command line tool.
And it provides more granularity than Activity Monitor
because it doesn't tell you the accumulated statistics
for the whole program, but instead it gives you information
for each region and each page of your virtual memory.
For each of those, it tells you the type of the
memory, it identifies what kind of protection you have,
and more importantly, it reports if it's resident and dirty.
There's another really good thing about using VM
Tracker, which is it helps you check your work.
You need to be proactive about checking to see that
your memory warning handler actually does its job.
And what you can do is you can use the
simulator to manually trigger a memory warning
and then watch your virtual memory usage
using VM Tracker to see how your app responds.
And it's time to show you a demo to see how to do this.
OK, so we're back at the demo machine.
Let me quit this.
All right.
So my -- here we go.
So in the same reader application, I have
my delegate, which is where I'm supposed
to implement my memory warning handler, but as you can
see it's blank; I don't know what I'm supposed to release.
So let's run it inside of Instruments to find out.
The Allocations template's the one you're going to want
to use because it also includes the VM Tracker instrument.
So I'm going to choose that one.
And you'll notice that it's initially
not giving you any data.
Well, the reason is because it's set up to manually
take snapshots or you can actually tell it to do
that automatically and you can even
change the interval at which it does that.
Great. Look, it just populated that
and it gave me this information.
Well, this is the summary review.
It tells you for all the different types of memory how
much of it is resident and how much of it is virtual
and also its protection and more importantly, look right
down here, it actually tells you how much of it is dirty.
But this isn't the granularity you want.
You want even more granularity.
So you go to the regions map and here, for every
single region it tells you that information,
specifically it tells you for each range how many
bytes are dirty, how many bytes are resonant,
what's the total size, and also once again, its protections.
And it also tells you the associated
file with each of those ranges.
That's really, really useful.
You can see it starts off with your
application, followed by all of its libraries.
And if we keep on scrolling down,
we know that this is a reader app
so we should eventually see the text files
associated with the books we're reading.
Where are they?
There they are, great.
So I'm going to highlight "History of the United States".
You see that we have -- it's a memory-mapped
file and that currently the dirty size is zero.
That makes sense because all we've done is actually
loaded it to be able to get the author and the title
out of the text file but we actually
haven't displayed it yet.
And it turns out that our application
is not just displaying it,
but it's actually decrypting the
encrypted version that we have on disk.
So once we actually display it, it's going to
have write the decrypted part back to memory.
So let's actually watch it do that.
So I'm going to focus in on that file and it's on FBCC.
Great there it is -- "History of the United States".
And I'll watch what happens when I actually click on this.
Sure enough, I'll focus and you will see that at that
address range, it changed from map to VM allocated
because it was loaded on copy-on-write
and all of it became dirty.
This is exactly the sort of memory we should be cleaning up.
But I haven't actually implemented
anything -- the memory handler.
So I need to go ahead and do that.
So let me stop this, quit it, and
-- so what should I do here?
Well, I basically need to tell the library to
give back to the system all memory associated
with the books that I'm not currently looking at.
Well, sure enough, I've already written
a convenience routine that does that
and I have it right here and I'll just implement that.
Great. Let me build it.
And I'm going to run it again in the exact same way.
OK. So I switch over to the VM Tracker.
I do a snapshot.
There we go.
I switch the region's map and I'm
going to go back down to my text files.
I'm going to follow all the same steps.
"History of the United States".
This time it's a 104D.
There it is.
Once I click on it, I'm going to see
that memory switch to VM allocated.
And I'm now going to go back.
At this point, if I receive a memory warning,
I should give that memory back to the system.
Now, the next step is to actually
simulate the memory warning.
Well, it's right here in the iPhone simulator.
I do that.
And what I want to see, actually, is I want
the memory to go and sure enough it has.
That's how you can check your work.
So VM Tracker is a great -- [applause] -- so VM Tracker
is a great tool for helping you identify all the pages
that are resident and dirty in your
application and also check your work
after the fact that you've actually done a correct job.
And with that, I'd like to invite Daniel back
on stage to talk about our last memory issue.
[ Applause ]
>> Daniel Delwood: Thank you very much, Victor.
Last thing I want to talk about is using autorelease
properly and give you some tips and tricks.
So first of all, memory high-water mark
really does matter in your application.
If you're on a Mac, this means
that you're going to cause paging,
on iPhone we've already spoken about memory warnings.
So you can use the Allocations and
VM Tracker instruments graphs --
those graphs that we showed -- to
identify spikes in your memory usage.
Now, what do they look like?
Something like this.
And so if you see it in really big spikes like this,
you'll notice that maybe it's using 10.6 megabytes.
Well, that's a lot of memory.
And many images loading and that sort of thing
will actually cause it to spike even higher.
So what you really want to do is
lower those spikes as much as possible
and keep your memory usage as steady as possible.
Better would be a graph that looks like this.
Now, you can accomplish this by using more
granular autorelease pools by nesting them
and perhaps even avoid autorelease objects at some points.
Now, there's definitely good places to
use autorelease objects when you have
to return values from methods and things like that.
But the key is being smart about it.
So let's talk about loops.
Loops are a really good place to be careful of this.
So in this example, what we have is we have a database
of employees and all we're doing is we're looping through
and trying to separate out the employees by their group ID.
Great, it's a simple idea.
But you'll notice that the loop invariance,
database.lastEmployee.number, well,
that's actually calling a property on the object.
And lastEmployee could be a new autorelease
object every time, especially if you're loading it
from a SQLite database, you know, it's something
you need to be aware of and be careful of.
In this case, it's really easy to fix
-- you just move that loop invariance
up a line right before the loop and
don't call it every single time.
Another one, some things like selectedRowIndexes
on table view.
Well, the table view can keep its
selected indexes any way it wants.
It can keep it in malloc buffer, it can keep them in
immutable index array, that's an implementation detail.
But the key is: however it does keep it, it probably
will need to return you an autoreleased immutable copy
so that you don't mess with its internal data.
So in that case, calling selectedRowIndexes is returning
you a new NSIndex set every single time you call it.
Now, in this case, we're not even actually
modifying the table's selected rows.
So we can also move that up and out of the loop.
And finally, one very, very common thing that you can
do and very easy thing that you can do is to avoid some
of these convenience methods inside of loops.
So numberWithInt, date, those sort of things off
the class will return you an autoreleased value
and that will grow your pool.
If you run this loop 10,000 times, that's 10,000 NSNumbers.
And so since we're just using it as key here, what we could
do, the line before say number equals number alloc init
with group ID and the right below our use of
it in objectForKey, just say "number release."
So it's taking a little bit more control of our memory
management based on the spikes that we see in those graphs
and even the statistics you can see
in the Object Allocations instrument.
Finally, I want to point out that there's no
magic here; this is just a delayed release.
Autorelease isn't doing anything under the covers
that's special and you can even see this in Instruments
when you look at the rough counting histories.
So, highlighted here is an autorelease event
but you'll notice that just in the bottom right,
there's a release event two down and that's
where the autorelease pool gets drained.
And so this is how you can sort of see when your
autorelease pools pop and fine-tune your usage better.
All right to summarize: memory's a limited
resource -- you know this but it's also very, very,
very important that you pay attention to it;
Instruments can help you and is a great tool,
lots of different facilities inside there; and you
should really work to avoid wasting and misusing memory
and even in the case of abandoned memory, you're going to
have to actually put in some work to get those scenarios
and make sure what you think is happening actually is.
And finally, please be proactive -- profile
your app before the problems happen.
Now, before you tune out, I'd like to direct you to
our Developer Tools Evangelist for more information,
the Instruments documentation, and
really plug the developer forum --
this is a lot people who have gone through the
same problems, faced the same challenges you have,
and they're very, very helpful and give advice on these
forums, and we also hang out there when we have time.
And finally, for related sessions -- you
might need a time machine for some of these --
but the last ones, the performance optimization on iPhone OS
and the automated user testing are coming
up, well, actually the last one isn't.
That is all.
Thank you very much for coming.
[ Applause ]