Frameworks • OS X • 48:41
Objective-C powers Mac OS X, providing a truly dynamic language upon which to build exciting applications. Mastering Objective-C will make you more productive, and provide greater insight into the Cocoa frameworks. Discover advanced techniques using garbage collection, blocks, and other new Objective-C features that will help you wring the most performance out of today's multicore Macs.
Speaker: Greg Parker
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
[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.
[ 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.
[ 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 ]
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 ]
[ 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.