WWDC2010 Session 144

Transcript

>> Greg Parker: Good morning.
And welcome to one of the last sessions of WWDC 2010.
This is Advanced Objective-C and
Garbage Collection Techniques.
My name is Greg Parker.
I'm the Runtime Wrangler for the Objective-C Runtime.
So, let's get started.
What are we going to talk about today?
We're going to talk about the two faces of Objective-C.
You get two Runtimes for one price of this language.
We're going to talk about some techniques you
can use in you code with Language features
and Runtime features that you can take advantage of.
For those of you using blocks including all of you with
new in iPhone 4 and iOS 4, we're going to talk about some
of the low-level details of blocks and some of
the edge cases of how to use them correctly.
And finally, for you Mac garbage collection users,
we'll talk about optimizing garbage-collected memory
and how to use Instruments in the Xcode preview you have
today to make your garbage collection code work better.
The two faces of Objective-C.
There are two Objective-C Runtimes.
There's a Modern runtime which is used for
most of our platforms and architectures now.
I say Modern because it was rewritten several
years ago when we started 64-bit development,
and is designed to support new features that
the Legacy runtime which dates back to next step
in the 1990s could not support
for binary compatibility reasons.
Until this week, this was what the diagram looked like.
The Legacy runtime was on 32-bit
Mac in the iPhone Simulator,
the Modern runtime was on 64-bit Mac in iPhone devices.
For those of you on iPhone, you know that the
mismatch between devices running one runtime,
simulator running the other runtime was a problem.
This week we fixed that.
Shazam! The iPhone Simulator--
[ Applause ]
Thanks to heroic last minute work by the Simulator team,
now runs the Modern runtime, supports all the same features
of Language features, runtime features
that are present on the iPhone devices.
For those of you on 32-bit Mac, well, sorry.
So, why do you care about the runtime?
You care about the runtime because it affects
what language features you can use in some cases,
C++ compatible exceptions, non-fragile istance variables.
And some of the new features I'm going to be showing
you today are only available on the Modern runtime.
So, if you're developing for Mac OS, and if you still need
to support 32-bit, then you're stuck with the legacy runtime
on one of your application platforms, so you
have to be careful what features you use.
For the iPhone developers or maybe I should call
them iOS developers now, I'm not quite sure.
Now, as of this week, you don't care anymore.
You can use the Modern runtime everywhere.
It's present on all devices you run on, and it's present
on SDK 4 simulators both the 3.2
simulator and the 4.0 simulator.
In particular, what that means for iPhone
developers is you can build a single binary
that runs on both simulator versions.
So, you can test your universal iPhone, iPad app
without recompiling, without changing SDK targets.
Language and Runtime Techniques.
I mentioned some new language features.
I'm also going to talk about a couple of old
language features you might not have seen.
Some of the advanced techniques I'm going
to talk about include: Writing code,
not writing code, and not executing code.
So, this is really low-level nitty gritty session
but you'll actually get some more detail there.
Writing code using class extensions
to organize the code the way you want
to get access control splitting
among multiple files, et cetera.
Not writing code using synthesized properties.
Let the compiler write accessors
and instance variables for you.
Not executing code, using weak-linked classes to write
an application that runs on a newer OS uses the features
of the newer OS, but also is backward
compatible with an older OS version.
It does not have those same features.
Let's start with class extensions.
What is a class extension?
Not all of you have heard of this, I'm sure.
A class extension is a second interface for your
class, or a third interface, or a fourth interface.
It looks like this here on your right.
It looks something like a category with no name.
If it were a category, there'll be on
those parentheses, there's nothing there.
In the class extension, you can declare
additional methods and additional properties
that are not present in your public interface.
So, it gives you a chance to have a private interface with
extra features or an internal-only interface for your--
some of your clients, but not other
clients, that sort of change.
It could be at a different header file if you want, but it
shares the same implementation as the rest of the class.
So, let's look at an example of what
you can do with a class extension.
This is a simple PetShopView class.
And by the way, I should mention,
I'm a low-level programmer.
The only thing I know about KVC is what the
letters stand for, so don't take this data
and method layout and use it in your own code.
Anyway, we have our PetShop class.
It stores kittens and puppies that we sell.
We have a property for the puppy food that we're feeding
the puppies, and we have a method for feeding the snake.
Clearly, feeding the snake is a risky touchy operation.
We don't want somebody doing this.
We don't want to starve the puppies.
We don't want them to feed the
snake with inappropriate items.
So, we have this interface, but
all of our details are public.
We don't want that.
We can use a class extension to
hide some of the declarations.
For the puppy food property, note that in the
public interface, the property is readonly,
but in the private interface, we've
redeclared it as readwrite,
which means publicly they can only read
it, but internally, we can write to it.
Secondly, we have our snake feeding
method which is now a private method.
There's no declaration in our public
header, but we can use it internally.
What's new is being able to do this, declaring
your instance variables in the class extension.
[ Applause ]
I could see some people already
recognize what this will do for you.
What this means is your interface does not have to
include your private instance variables anymore.
You can-- your public interface only
includes what you really want to be public
which is almost never your instance variables.
So, ivars in Class Extensions, when can you use these?
It is available only in the Modern runtime, which is
why I explained runtimes to you a couple of minutes ago.
It is only available in the LLVM compiler.
GCC is a difficult code base, and we're
not going to add this feature to it.
In the Xcode Preview, this feature is not on by default.
There's an extra compiler flag you need to set.
I think Xcode 4 has a-- there's a
setting in the build settings for this
or you can set other C Flags using these options out there.
Next topic.
Not writing code using @synthesize.
Let's look again at a modified PetShopView class.
Here, we're looking at the puppy food property.
This is the normal implementation of a property.
We have our declaration in our interface or possibly
a class extension, and we have our implementation
where we use @synthesize to tell the compiler
add the method for us or get our method,
add a setter method that match
the declaration of the property.
You can also do this.
We don't have an instance variable for our property.
Instead, we let @synthesize add
the instance variable as well.
So, now @synthesize is doing three things -
adds a setter method, adds a getter method,
adds an ivar with the same name as the property.
This has been available since Snow Leopard ship.
Although for a while there was a bug where you
could not use the instance variable directly,
now that works on all of our compilers.
What's new in Xcode 4 is being able to do this.
You don't even write @synthesize.
You write your app property and nothing else.
[ Applause ]
The compiler builds all the rest of the code for you.
Basically, what this does @synthesize
is the default for most properties.
Now, the compiler writes your setter
method and writes your getter method.
It writes the instance variable for you.
All you did was write @property in your interface.
Synthesize by default.
Again, it only works in the Modern runtime.
And again, it only works in the LLVM compiler.
And again, in your Xcode Developer Preview, it only works
in the LLVM compiler when you turn on a compiler flag.
Of course, you don't always want
to synthesize your properties,
so you need to use one of the other alternatives
to synthesize that we've had all along.
If you write accessor methods by hand,
the @synthesize will not take effect.
The other alternative is to use the @dynamic keyword which
tells the compiler, "I'm not writing a method, but compiler,
please do not write a method for me either.
It'll be provided somewhere else
at runtime or at compile time."
For example, it could be a forwarded message or you
could use dynamic method resolution to create the method,
o perhaps you're doing a Core Data NSManagedObject.
An NSManagedObject will create accessor methods for you.
In any of those cases where you're relying
on somebody else to write the method,
we synthesize by default you should make sure you
have @dynamic describing each of your properties.
If you didn't have @dynamic previously and you didn't have
@synthesize, you should have gotten a compiler warning.
So, as long as you're actually paying attention to
compiler warnings, that won't be a problem for you.
Last topic for language and runtime
techniques is weak linking.
Weak linking is a way to use an operating system feature,
but still run your app on OS versions
that do not have that feature.
So, this is a C example of weak linking.
We have a C function called NSDrawNinePartImage.
This is added in AppKit in Snow Leopard, I think, and
does not exist on Leopard, I think it's added in Leopard,
I forgot, anyway, I don't program
AppKit, I program a low level.
In any case, this is how you would use it if
you also want it to run on an older OS version.
You check if the function pointer is NULL.
If the function pointer is NULL, you don't call
the function, you have to do something else.
You're-- ideally, you want to do the same thing
with Objective-C code and Objective-C classes.
This is what you have to do today if you want
to use weak linking for Objective-C classes.
UIPopoverController is a UIKit class that was added for the
iPad in 3.2, but did not exist on iPhone and iPhone 3.1.
So, any of you who are iPhone developers know if you're
trying to write a single application that runs on both iPad
and iPhone, you need to do something
with UIPopoverController to be able
to use it and still run on an iPhone device.
So, this is what the code looks like.
You need to use NSClassFromString to look up the
class by name, store that in the local variable,
and then always use the local variable
when you want to message the class.
You cannot actually send the message to
the class directly because it'll fail
at link time, and your program will not launch.
In addition, in the second example on the bottom, you
cannot subclass a class that may be absent at runtime.
If you try that, if you try to create your own controller,
there's a subclass of PopoverController, it just crashes.
If you try and use your subclass,
well sorry, it just doesn't work.
So, you'd rather have your code look something like this.
In the top example, this is much cleaner.
You're sending messages to the class
directly, you don't need ClassFromString.
You don't need a local variable to store the
class pointer that you looked up earlier.
You could just send the message to the class.
The class will either respond normally
because it is present.
Or the class will act like a message to nil as if the
class weren't there because in fact it's not there.
So, in this example, I'm sending the class method
to the class to see if it's present or not,
either that will return my class pointer or it will
return nil because I'm running on an older OS version.
Secondly, you want subclasses to work.
If you create a subclass of a class
that is not there or may not be there,
you want the subclass to act the same way
not the subclass to basically hide itself,
remove itself from the runtime if
its super class is not available.
So, in this case, I'm sending alloc
and init to my new class.
Of course if my class is nil, then
alloc returns nil, init returns nil,
I don't get an object back and
I can check for that at runtime.
So, this is the method you want to be able write your code.
The good news is we are on our way to supporting this.
So, summary of weak linking again - simplifies
deployment to multiple OS versions on iPhone and iPad
or different Mac OS versions when you want to use
a new feature but still be able to run without it.
The bad sign, bad news is implementation forthcoming.
Here's the gory details behind implementation forthcoming.
Compiler support, Xcode 4 all the compilers have the
necessary compiler work to support weak-linked classes.
Runtime support, you need to be able to have the
runtime not to crash when it sees a null pointer.
iPhone OS 3.1 support this.
Mac OS does not yet support this but it will in the future.
The big problem is SDK support.
In order for weak linking to work, each class in the
header file needs to declare when it was available,
which OS versions have worked with,
which OS versions it was not available.
So, if you've looked in the header, you'll be seeing
availability macros, you know, OS X available starting,
iPhone available starting, and that sort of thing.
Those are the tags to tell weak linking
when a class is and is not available.
Unfortunately, none of the headers that we've shipped on
any operating system actually declare this information.
So, you could use weak-linked classes.
You could run on iPhone OS, but it
only works with your own classes
which you probably don't need to
weak link against in the first place.
So, I wanted to have a big button on this
slide, this is WWDC 2011, but I didn't get that.
So, hopefully when we're here next year, we'll be
able to tell you that this feature is supported
and has been supported back at least to iPhone OS 3.1.
So, that's all for a sort of language and runtime features.
Let's talk about blocks because blocks are cool.
And for those of you on the iPhone, blocks are new.
And for those of you who didn't pay much attention
to Snow Leopard last year, blocks are also new.
And for those of you who haven't been
to WWDC before, blocks are also new.
And for those of you who blocks
are not new, blocks are hard.
[ Laughter ]
So let's look at some details of blocks, and to show you
an example of block memory, what happens to block objects
and the variables they reference as you use blocks.
We'll talk a little bit about copying
blocks, when and how to do it.
And we'll talk a little bit about block storage
variables which are a really neat feature of blocks
so you can't get any other way,
but are confusing to say the least.
Let's look at an animation of blocks in action.
You may have seen a similar animation to this in
some of the other presentations that cover blocks.
That's because we want to show this
animation to you over and over and over again
until you understand because it's
a really important feature.
We have some code that creates a block on the stack,
and then it copies that block a couple of times.
We can see our stack itself, which at this initial
point, has a local variable called captured
and a local block scope variable called shared.
So, let's see what happens as we execute the code.
The first thing we do is create the block on the stack.
It creates a block on the stack.
It creates a copy of most of the variables it uses, but
it does not create a copy of the block scope variable.
So that's the difference between your block
scope variable and an ordinary variable.
So, we have our block object on the stack.
It has made a copy to the variable called captured,
and that variable captured is const inside
the block, you cannot change that variable.
Next our original function changes
the value of its captured variable.
So, the captured variable in the stack now becomes 20,
but the blocked object created a snapshot of the world
as it was created, so it does not see
that change to the variable captured.
It has its own copy which stays at 10.
Now, let's copy the block.
Copying the block makes a copy of the
block on the heap instead of on the stack.
The other thing it does is move any block scope variables to
the heap because the lifetime of the block scope variables
and the lifetime of the block need to be tied together
because the block scope variable needs to live at least
as long as the block does so the
blocking can do to use that variable.
In this code, we make a second copy
of our stacked block which works.
We can do that.
There's a big X here because you
really don't want to most of the time.
Most of the time you want to create
only one heap copy of the block
and not duplicate it any more times than necessary.
So, let's change our code a little bit.
As we see in yellow down there, we
now have our block2 being copied.
So, we're creating a copy of the heaped block
rather than copying the stacked block again.
When we do that, there's no new copy, instead, copying an
already copied block just synchronize the retain count.
We don't need a second block object next
to the first block object we already have.
We'll just reference the same block object twice.
Note that in both these cases, when we
copied block, we did not copy the shared
or the block scope variable, the purple one called shared.
All the blocks, all the copies of these blocks
and the original function all point
to the same copy of that variable.
So, let's talk about cleanup.
We have the stuff on the stack.
We have the stuff on the heap.
Some of it is going to live longer than
others, what happens when it goes away?
One option is that the block objects are destroyed first.
When the block objects are destroyed first, the
ones on the heap, I mean, the copied objects.
For example, you passed them to dispatch
sink and dispatch around the blocks,
and then it was done, so it deleted the blocks.
That looks like this.
Your block object on the heap goes away because its retain
count reached 0, but the blocks that go variable stays alive
because that function still needs to use it,
still needs to point to that shared variable.
And in fact, it might be reading the
value that the blocks wrote to it.
The other example of cleanup, let's
go back to the pre-cleanup state.
The other example is the function returns
first while the blocks are still alive.
You can do this.
That's the really neat things about blocks 'cause they
survive the scope of the function that created them.
So, for this example, perhaps you called
dispatchAsync to call the block asynchronously,
and then the function returns before the blocks finish
running while the blocks are still on the queue.
What does that cleanup look like?
Well, of course, the code in the stack variables go away.
Oh, the code doesn't go away, it
just stop running in the function.
You know what I mean.
But the block objects are still alive.
They may still be running.
They may be on a queue to be run later.
And the blocks covariable also stays alive
because those blocks may need to use it.
This is why we copied the block to the heap.
This is why we moved our blocks covariable to the heap
because we may want to use them after the function
that created them goes away, after the
stack frame that created them goes away.
When the blocks do finally get deallocated,
they go away and the block variable goes away.
The blocks covariable lives until
the last one leaves and they turn
out the lights and free the heap memory if necessary.
So, that's block memory.
That's the sum of what's going on behind
the scenes when you create a block,
when you execute a block, and when
you create copies of the block.
So, block copies that we just saw.
You want to use them for two reasons.
You want to copy a block if it needs to
outlive the function that created it.
You want to copy a block if you want
to make it runnable on a second thread.
In particular, if you run any garbage collection, you
must copy it if you want to run it on a second thread.
If you don't copy it, the garbage collector, in a
particular the thread local collector will be confused
that you created this block on
one thread, and then suddenly,
it's running on some other thread that it was not expecting.
You must copy it to say, "Hey Mr. Garbage Collector,
this block may be run on some other thread."
So, that's true.
Even if you're going to run asynchronously, even if the
function that created it will survive longer than the block,
you still need to copy and to tell
the garbage collector what's going on.
How do you create a copy of a block?
The easy way to create a copy of the block is
use the copy method and the release method.
Just like any other Objective-C object, all blocks
are objects, they respond to these messages.
There are also C functions for copying and deleting blocks.
If you're writing pure C code, you
can use those C functions instead.
You should prefer to use the methods instead of the
functions especially if you're using garbage collection.
Because of course in garbage collection, the release
method does nothing because it doesn't need to.
So, it's easier to copy the block and then do
nothing, let the garbage collector clean it up,
as opposed to doing the C function to copy the
block and then you must manually call the C function
to release it even if you're running garbage collection.
Let's talk about block scope variables and the
__block keyword, that's two underscores underneath.
You can't see it with this font.
Block scope variables, as we saw on the animation,
behave differently than a normal stack variable.
They behave differently than a global variable.
And they behave differently from any
local variable inside the block itself.
So, block is a storage class for those of you
who know the C language and Objective-C language.
Examples of storage class are static.
A static variable, a global variable is a
particular storage class that differs from register,
which in the old days would tell the compiler, "Put
this variable in the register instead of on the stack,"
or oppose to auto, which in the really,
really old days, was a hint to the compiler
to put it on the stack rather than somewhere else.
So, block is not in registers, it's not on the stack,
it's not a global variable, it's something different.
In implementation wise, it's usually on the stack
around the heap, but you don't need to know that.
It's in its separate storage area.
A block variable is mutable unlike any other
variables inside a block that are captured
from the function that created it or from other globals.
So, the block variable is mutable which means the block
variable can write to it, and the function will see
that data because block variable is shared.
It is shared with the function that created it.
It is shared with any blocks that
reference it at any copies of those blocks.
There's only one copy of that block variable
shared among everybody mutable for all of them.
Some other gotchas about block variables.
If you have a block variable that is an Objective-C pointer,
that value is not retained which
differs from the captured objects.
I didn't show this in the animation, but a
captured variable, the block will retain it
if it's an Objective-C object and
release it when the block is destroyed.
A block scope variable is not retained.
You're on your own for managing the merry
management if you're not using the garbage collector.
As we saw, a block variable may move from the stack
to the heap, which means its value maybe copied.
If you're writing C++ code or objective
C++ code, that's an important fact.
If you have a copy constructor or
something like that, it may be copied.
Also, you don't want to take the address of any of
these variables because if it does move from the stack
to the heap, the address you took is no longer
valid, you'll crash if you try and use it.
Finally, block arrays are not allowed.
Block arrays are not allowed because of
some corky edge cases in the C language
or the C compiler does not know how big your array is.
It knows it's an array, it knows it's a
pointer, it doesn't know how many bytes it has.
And the C++, it doesn't know how many
elements would need to be copy constructed
when the block is moved from the stack to the heap.
So, you can't actually use an array to do that.
As they work around, you can usually use a struct
containing an array, and use that as your block variable.
And then the compiler will have enough
knowledge to copy it when it needs to.
What do you use block scope variables for?
You use block scope variables because they are
shared, because they live as long as the block
or the function whichever survives longer.
You can send values between different calls to the
same block or different copies of the same block.
You can return values back to your caller.
For example, if you're calling an API that takes a block
that returns void, of course, you can't return anything
to your caller, but you could create a block variable
in your caller, have the block write to it as it runs,
and then the caller can pick up that
information when it is completed.
Of course, with any shared data, and a block, of
course, is shared with its function in any other blocks,
you need to be aware of thread synchronization.
You need to make sure that you
don't have multiple threads writing
to the same variable and clobbering each other results.
You need to make sure that if somebody writes
to the variable and somebody else reads,
that there are some synchronization so that the reader knows
to wait until the writer is done, all that sort of thing.
Same thing if you had a shared global variable or an
instance variable that was shared from multiple threads.
So, let me go back to that.
The thread synchronization mechanisms are, of
course, the same ones you would use anywhere else.
You can use a pthread lock, you can use Objective-C locks,
you can use dispatch queues, you can use atomic operations,
any of that stuff works just like
it would work anywhere else.
The thing to remember is to remember
that you have to do this.
Finally, let me talk about garbage
collected memory and optimizing it,
in particular, optimizing memory you don't want.
The garbage collector is not magic.
It deletes a lot of memory you don't want, but
it cannot delete all the memory you don't want
because it doesn't know what you know in
your head how you want the program to work.
So, we can define two classes of memory you don't want.
This applies to garbage collection as well as
non-garbage collections, useful terminology.
We could define a leak.
You know, leak is an allocation
that nobody points to anymore.
It is not referenced anymore.
Clearly, the program can't be using that memory.
Clearly, you don't want it.
Oh, as you referenced it.
But sometimes, you have memory that is referenced.
This is abandoned memory.
Its allocation is referenced.
There's still a pointer to it somewhere, but
nobody is actually going to use it anymore.
It's done.
And nobody is going to actually use that
reference for the rest of the program.
So, leak detectors.
The leaks tool, the leaks instrument.
They can find leak memory because they scan
for allocations that are not referenced,
but they don't find abandoned memory because
there are references to the abandoned memory.
The leak detector doesn't know which ones you will use
in the future and which ones you won't use in the future.
OK. Let's try a garbage collector.
Garbage collector solves all memory problems, right?
Well, no. A garbage collector automatically
deletes leaked memory.
That's what it does.
If an object is no longer referenced, or a group of
objects together are no longer referenced, it deletes them.
That's garbage collectors are for.
They're very good at it.
Unfortunately, they cannot help you with abandoned memory.
If you have a pointer to an object, the garbage
collector does not know whether you'll be using
that pointer in the future.
So, that's the problem with garbage
collector or even non-GC code--
is we have tools and techniques and
garbage collectors to solve leaked memory,
but the abandoned memory may still be a problem
and it can still kill your applications,
swamp your machine just as fast as an actual leak would.
Some examples of abandoned memory.
And let me say, inside Apple, we
spent significant time at the end
of Snow Leopard development looking specifically
for leaked memory and abandoned memory.
These are some real world examples we saw.
You probably have some of these
examples in your code as well.
The first example, a write-only cache,
what the heck is a write-only cache?
A write-only cache, and I'll withhold
the names to protect the guilty.
A write-only cache is where you cache
some information, perhaps some images,
and then you never actually use that cache.
If you reread the same image, you reread the
same image, and put it in the cache again.
Needless to say, this is not a recommended technique--
[ Laughter ]
But the garbage collector and the leak detectors
won't find it because your cache still has pointers
to all these memory you allocated,
but you'll never going to use it.
Similar example is an add-only container where we
have a container or a cache where we add objects
to it, but we never actually remove from it.
Eventually, this container will grow and grow
and grow, and eventually, occupy all your memory.
An example of this was on iPhone previously
in older OS versions, the imageNamed function.
I'm sure all you guys will boo and hiss at the old behavior
of imageNamed, which would allocate an image read from disk,
and then never get rid of it even if your
program was not using that image anymore.
So, the problem was, you know, you get
a memory warning 'cause you're running
out of memory 'cause you have all these images, but
the imageNamed cache will hold on to all of them.
Another example is a pointer to the current document.
This is a particular problem in GC code,
but it's also the problem in non-GC.
So, you have a variable storing the current document.
Let's say you closed that document,
but you don't erase the pointer.
Now, there's a variable that still points
to the document that should be closed,
which means the garbage collector will not delete it because
there's still straight pointer out there pointing to it.
This was an example when we first were
originally writing the garbage collector.
We tried TextEdit as our-- a test application.
We try and run TextEdit with the garbage collector.
There was exactly one line of code that needed to be changed
not was because it had a pointer to the current document,
it did not nil it out when the document was closed.
Final common example is an un-drained autorelease pool.
An autorelease pool will act like an
add-only container if you don't drain it.
So, you can accumulate a large number
of objects in your autorelease pool.
You don't need them clearly if the
autorelease pool were to drain, they'd go away.
But in the meantime, the leak detector won't find them.
And if you've organized your code so it doesn't
drain promptly, then those objects will live longer
than you want, occupying memory you don't need them to.
So, we have some tools for doing
abandoned memory better, for finding it.
So, let me show you an example of that.
I've written a program here that
leaks memory and abandons memory,
and I'm going to show you how to
use Instruments to find that.
In particular, some of the details you
need for garbage collected program.
So, here's my application.
We have a leak button.
This leaks some memory.
We have abandoned button which abandons some memory,
puts it in an array, doesn't get rid of it ever.
This is a garbage-collected program, so
leaking is fine, but abandoning memory is not.
So, I'm running the program in Instruments
with the garbage collection configuration.
We have our object graph, we have our object allocations,
and we have the garbage collection instrument
showing us when GC operations occur.
I'm going to set my allocation's graph to
show the total bytes currently allocated just
because it makes a pretty demo you can see.
So, let's leak some memory.
Of course, this a garbage-collected
application, so leaks should be mostly harmless.
And in fact, they are.
We can look closer at this graph.
And we end up with the stereotypical sawtooth pattern.
Every GC program would look like this.
It'll allocate a bunch of memory, then the
garbage collector kicks in, memory drops,
allocates more memory, garbage collector drops.
You can see the garbage collector going on down here.
This tick marks for when the garbage
collector collects some memory.
Now, if these were a non-GC program,
the graph would not look like this.
Instead, these are real leaks, and the
graph will go up and up and up and up.
So, let's stop leaking for a while.
Let's abandon memory instead.
We're still allocating objects.
But now, we're keeping pointers to those objects alive.
And guess what?
The graph goes up, the garbage collector
tries to do some work but nothing happen,
it tries to do more work, nothing happens.
You can't solve that memory because
there are still pointers to it.
So, the trick here is be able to find which
memory was allocated and not destroyed.
And here is where Instruments can really help us.
There's something in Instruments knew called a heapshot.
A heapshot is a snapshot of your
heap at a particular point in time.
And the trick to finding abandoned memory is to take
multiple heapshots and compare them in multiple points
in time around your program's operations.
So, take a snapshot of your heap as
it is at a particular point in time.
Do some work in your application that should result
in no change in memory, and then do another heapshot
and compare them and see how the
memory in your application change.
So, the usual way to set this up in testing is to have a
cycle that should do work but should not create memory.
For example, open the document and then close it again.
There should be no change in memory once that's done.
Open the document, do some edits
to the file, save it, close it.
Again, there should be no changes
to memory after you've done that.
In my toy application, I have a cycle ones button,
and that does some leaks in some abandoned memory.
Now ideally, that would have no change in
the heap, but because of abandoning memory,
it does have a change in the heap
and Instruments can find that for us.
Let's see that in action.
We hit the cycle button.
We see a big spike in memory.
Because I leaked a whole lot of memory
and then the garbage collector kicked in
and cleaned it up for us, so this looks pretty good.
Of course that time, Instruments didn't cache the spike.
Instruments only sometimes will see
the spike before it goes away again
because the garbage collector is
pretty fast most of the time.
So in these cases, it looks like that the
graph goes way up and then comes back down to
where it started, but instrument is not fooled.
Instruments knows-- can tell that your--
that your altercation is allocating memory that isn't
going away, that that graph is not in fact flat.
So, heapshot is part of the allocations instrument.
It's this button here, mark heap-- let's do that now.
Now, we have the baseline.
All the objects are allocated up
to this point in the application.
Now, we can run a cycle and take another heapshot.
This compares the difference from the second
point in time to the earlier one, to our baseline,
and we can see our heap grew almost 400
kilobytes over the duration of that cycle.
Let's do it again and cycle again.
Let's cycle a bunch of times.
So, we get a bunch of memory spikes and the memory does not
go back down to where it was and heapshot can tell us that.
The second heapshot is being compared to the one before
it, so it can see from the start of the application
to the first heapshot was 300 kilobytes, the next one
was 3.7 megabytes, I guess I hit the button ten times.
And so, you can do this in your
testing matrix in your testing work.
You can even set this up automatically if your application
has scripting capabilities, something like that,
is you can do the work and create snapshots and do the work
and create more snapshots and see what memory is leaking.
Of course, if memory doesn't leak, we
get a heapshot that looks like this.
No memory leaked that time because
I didn't hit the cycle button.
We can run leaks.
The garbage collector should be cleaning
this up, let's see if it actually does.
They can create a heapshot.
And yup, pretty much all the memory is gone.
We can create more heapshots.
All the memory is gone.
So, the garbage collector, while we're
leaking is doing the work we needed to.
Of course in Instruments, you can look at this memory.
You can look at where it was allocated
from, what the objects are.
So, let's look at one of our heapshots and see what were
the objects that were allocated between the first point
and the last point and not destroyed
sometime in the middle or later.
So, we have some CFArrays, twenty of
them were allocated or not destroyed.
And of course, we can use Instruments to pull up our
stack trace of where they were allocated from, like this.
Now, we can see it's in my application, in the cycle
method, in the do-abandon method, we allocated some arrays,
and they were not destroyed between my two heapshots.
And of course, some Instruments, we can pull
up the file and it shows us the source line.
Here's my method that abandons memory, it creates a global
array, it adds stuff to it, and it never does anything else.
So, Instruments is able to analyze the heap,
show us the difference in changes to the heap,
and then show us the allocation points of those objects.
And now, I can decide should that object
be alive or should it have been destroyed.
And if it was not destroyed, why?
At this point, you can go to the rest of Instruments to
see if your garbage collected, who has pointers to it.
If you're not garbage collected, who
retained it and did not release it.
You can find out why is this memory been abandoned.
There's one final detail I want to show you
for garbage collection specifically in order
to use this instrument to the best advantage.
This is Xcode 4.
I created a scheme, run scheme to run in this instrument.
The thing I did is in the arguments,
I have an environment variable set.
AUTO_USE_TLC = NO.
What does this do?
This turns off the thread local garbage collector.
Because on Snow Leopard, the thread local garbage collector
and the heapshot instrument do not play nicely together.
In particular, the thread local garbage collector--
by the way, if you haven't heard of the thread local garbage
collector, it is an optimized mode for garbage collection
for objects that are allocated on one
thread and never moved to another thread.
So, your local variables, your temporary variables,
the garbage collector is extra fast at handling them.
Unfortunately, heapshot and the
garbage collector don't cooperate.
So, if you do not set this environment variable,
heapshot will think you have more objects allocated
than you really do because it won't notice that
the thread local objects have actually gone away.
It thinks they're still alive.
So, set this environment variable and you
will get better precision from Instruments,
and you will have better precision from the
leak detector in garbage collective code.
So, that's the heapshot instrument or the
heapshot addition to the allocation instrument.
It was available on Xcode 3.
It works much better in Xcode 4, and is
more tuned for the workflows you need
for analyzing garbage collected
and non-garbage collected memory.
So--
[ Applause ]
So, abandoned memory, you can find it with Instruments, you
can debug it with Instruments, and you can fix your code.
Of course, depending what kind of abandoned memory
you have, you need to use different techniques.
You might need to limit your cache sizes or
shrink your caches when you get a memory warning.
You may need an explicit invalidation protocol
particularly if you have a GC application,
your closed document code may need to go out and
erase some pointers that you don't need anymore.
You can also use weak pointers.
That was how we fixed TextEdit with this pointer to
the current document, we made it a weak pointer so that
when the document was closed, the garbage
collector erase the variable for us.
And of course, if you have autorelease pool problems, you
may need additional autorelease pools to do your work.
All these techniques work well with garbage
collected and non-garbage collected code.
It's just more important with garbage collected code
because the leak detectors do nothing
for you in garbage collected code.
Actually, that's not quite true.
Of course, in the garbage-collected application,
not everything uses the garbage collector.
Your objects may use the garbage collector,
but perhaps, you call malloc somewhere
and it uses just ordinary malloc and free.
In that case, the leak detector is still a useful tool
for you to run against your garbage-collected application.
If you do use that, you should also set
the AUTO_USE_TLC environment variable.
So, let me show you that environment variable again.
AUTO_USE_TLC = NO.
I don't know if there's any documentation
of this anywhere, but now you know.
[ Laughter ]
This is what you pay the big bucks for, to see
what other people on the internet don't get to see.
[ Applause ]
So, you can use leak detectors.
They work great in GC applications,
but only for the non-GC memory.
You need to use heapshot to find the
abandoned memory and help fix it.
So, that's it for Advanced Objective-C
and Garbage Collection Techniques.
What have we seen today?
We've seen runtime count and decrement
it Great for those here on iPhone,
not so great for those of you still on 32-bit Mac.
We see some old language features like class extensions and
@synthesize, but they've been given new twists available
in the LLVM compiler with your Xcode Preview.
We've explained some of the nitty
gritty behind block objects.
Hopefully, you can fix some bugs
and you have your block usage code.
And we've shown you how to optimize garbage-collected
memory using Instruments and some new heapshot capabilities.
For more information about the programming language,
about programming the runtime and its low-level details,
there's documentation, although I don't know if this is
AUTO_USE_TLC or not, available at Apple's Developer website.
And if you visit the Apple Developer
Forums, I'm frequently there as G.
Parker on the Core OS forums and some of the others,
and you can get some answers to your questions there.