Developer Tools • iOS, OS X • 1:02:43
Automatic Reference Counting (ARC) dramatically simplifies memory management in Objective-C. Discover how the latest advancements in the LLVM compiler can help you write less code when creating safe, fast applications. A must-attend session for developers of all skill levels, on both Mac OS X and iOS.
Speakers: Patrick Beard, Chris Lattner, John McCall
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Good morning, everyone. It's a wonderful Friday, very early morning for us all. But we're here to talk about Arc. So let's dive in. In a nutshell, what is Arc all about? Arc is about simplifying retain and release. I mean, everybody loves retain and release, I'm sure, but we think it could be better, it could be easier, it's more productive, easier to maintain, we can make Objective-C a higher level language.
That's really the idea of Arc. We think we've got something really amazing here. So if there's one thing that you take away from this session, it's that when Xcode 4.2 ships for real, you should move your applications to Arc. Arc is great technology and it's really the future of Objective-C.
So we're really excited about this. Let's talk about why you're actually probably here, though. It's because we have a small issue with applications crashing, right? Now, I know that your apps never have these problems, of course, but some other people's apps occasionally have some minor issues, right? And this can be everything from, you know, getting bad reviews because an application ships and people are complaining, but only in weird cases, right? It's only when that dangling pointer in the app happens to manifest in a really nasty way that crashes on some weird configuration.
These problems are really nasty, and some of them can lead to bad reviews, they can lead to your app being rejected from the store, I mean, there's all kinds of problems that can lead to crashes. And so we think that crashes are one of the major problems that we want to fix.
And crashes are the premier reason why applications get rejected from the store, so if we can fix this, it makes life better both for the end user but also for you when you're submitting your app. It means you're much more likely to get through the approval process smoothly.
So we started looking at this and what became really obvious is that memory management is actually really hard, right? Everybody has to do this. We've done it for a long time. All programmers have to do this. But if you look at the number of tools that we ship, everything from instruments, which has a suite of really great tools for debugging memory problems, all the way down to VM map and other really low-level tools, it's really obvious that while everybody understands memory, it's actually pretty hard. And when you start getting into the details of, okay, well, somebody made one little mistake somewhere in the application, tracking it back to the root cause is a lot of pain and suffering.
Now, you get really good at that, but wouldn't it be better to be spending that brain power building new features, right? So we think that memory management is a really important problem and something we really want to tackle. Now, ARC doesn't solve all these problems automatically, but it defines away a large number of the problems that are the root cause of these things.
So if you look at how Objective-C has been moving over time, Objective-C is actually a really interesting language. It's based, as Craig said on Monday, on pragmatism. The pragmatic start of Objective-C was C. C is a fantastic language. C is the performance base that has made the iPhone possible to be efficient and have good battery life and all of these wonderful things that we like. And then you look at Objective-C. Well, Objective-C, the first major step was to make C object-oriented.
[Transcript missing]
So, as we go forward, we talk about, well, okay, Objective-C is an amazing language, and it's been using retain-release. Retain-release is clearly good enough to ship amazing products with, right? You guys have done this with your apps, Apple's done it with its operating systems. The problem with Objective-C, though, or with retain-release, is that you're swimming in details, right? And you're forced to know things like the Cocoa naming conventions. Well, if you have a new method, that returns a plus one. If you have a copy method, that returns a plus one. If you have a get method, it doesn't, right? You have auto-release pools. You have things like block copy.
These things, experts, all of us, can handle. But particularly when you're new coming to the platform, and you haven't been dealing with this before, trying to wrap your brain around what an auto-release pool really is, when does that object get deallocated, not so straightforward, right? And so while you can certainly learn this, it's something that really is challenging when you come to the platform. Beyond that, when you're an experienced programmer now, you have many decisions you can make.
Should you use? Should you use auto-releasing creation methods? Or should you use allocant? Well, they both sort of do the same thing. But the first one's easier, and you might want to use it all the time. But on the other hand, you start feeling a little bit guilty because, you know, that's increasing your high watermark through auto-release pools. Well, this is certainly workable. But, you know, having to think about these details is really distracting from the real problem of writing apps.
So overall, my personal problem with retain and release is that it forces you to think about so many details all the time when you're writing your code, that's actually not contributing to the value of your app. Memory management is a necessary thing that everybody has to do, but it's really just the side thing that gets in your way of building a great app.
So the bigger problem though, and where we started with this, is crashes. So one of the major problems with manual memory management is that it gives you the opportunity to occasionally make a mistake. And a bug creeps in. And this means that if you aren't perfect all the time, which is something that humans are not great at, then, oh, well, your app crashes in an obscure case. You dereference a dangling pointer. You have a memory leak. These things aren't super common, but particularly if you don't have 100% knowledge of the Cocoa Memory Management rules, this can lead to major problems in an application.
And so what this really means is that we all end up being really good at debugging problems, and using these tools and understanding what the application is doing, what it's not supposed to be doing. And I mean, you guys are all smart people. Wouldn't you rather not be spending your time doing this? So the idea, and based on all this kind of information, we said, hey, well, let's see if we can make this better, right? Memory management, clearly something we can make better. What can we do? The first step in Xcode was the Xcode Stack Analyzer. The analyzer uses an exponential algorithm to walk through your code and find all kinds of bugs. And it's really powerful. It can find many different kinds of bugs, right? It has limitations, though.
One of the great things about the analyzer, in hindsight, is that we learned a lot from it. We learned a lot about Objective-C, and we realized that a lot of the things that we thought we knew about Objective-C were true, and some were false, right? We actually, it's a good way to verify some assumptions.
One thing we learned, in particular, is that some applications have leaks everywhere, right? This is usually because somebody, the person who wrote the application, doesn't know the Cocoa Memory Management Guides, right? And so we know this because they end up filing a bug saying, hey, the analyzer is completely broken.
It's saying I'm leaking everywhere. What's going on? And we look at the code, and it's like, well, actually, sorry, but you are. And so the reality that people are leaking memory, some people are leaking memory all over the place in their applications, really drove home the value of automating memory management, right? People should not have to deal with this. Another thing we learned is that Cocoa has really strong patterns.
Again, things like the naming conventions, which we believe to be strong, we believe that they were reliable, actually are. And so they're good enough that we can automate them and base new tools on top of them. And then the third thing we learned is that the analyzer does have false positives. It gets confused, okay? It could tell you, hey, you have a memory leak, you have a bug, when you actually don't, because the conditions that it thought could happen actually can't.
Now, in the context of an analyzer, that's fine, right? It says, hey, you might have a bug. And then you think about it, and then you say, no, leave me alone. In the context of Arc, this is completely unacceptable, because that means that instead of telling you you have a bug, it actually goes through and adds a release.
when it should not be released and now your application is crashing, right? And so learning the limitations of the static analyzer is actually really important and critical to making Arc 100% reliable. Because if the compiler is not bulletproof, you can't trust it. If you can't trust it, you really should not use it.
So the end result of all of this is that we said, hey, we want to automate this. This is a worthwhile problem. Instead of increasing the complexity of Objective-C, we're just going to define away a whole bunch of stuff you had to deal with, right? Of course, there are problems.
We have to make it perfect. We have to fix some performance problems in the analyzer. And that led to a different implementation approach for the Arc compiler. But we really transferred the interesting ideas from the analyzer. And so we said, OK, we really want to do this. So today we're going to talk about three things. First, I'll tell you about Arc. I'll tell you what the model is. I'll tell you about what programming it's like, what the issues are that you have to be aware of.
After that, John McCall will tell you, how does the compiler work? How does it reason about your code? How does it decide where to put a retain and release? Now this is something you shouldn't have to think about. And you don't have to think about. But hopefully it will be reassuring to know that the compiler is based on really simple rules and they can't go wrong.
And hopefully that will help with trust. And at the end, Patrick will come up and he'll show you how to actually convert your application. And what some common problems you might see are. And some things to be aware of with Arc. So let's dive right in. So Arc.
If you haven't heard yet, it's all about automating objects and the memory management for objects in your application. So the idea is really simple. The compiler is going to insert retain and release calls for you. This means it's just retain and release code like it always has been.
But you don't have to do the memory management. The compiler is going to do it for you. A major feature of this. This means that because it's just retain and release, it completely is interoperable with existing retain and release code. This means that your application can be Arc even if a system framework is not Arc. This means that if you have a .a file in your application, it doesn't have to be converted. You can convert your application and not all the code in it. So this interoperability is a major feature of Arc.
In addition to the compiler features, we've actually substantially improved the runtime in many ways. Performance, for example. Adding zeroing weak pointers, which we'll talk about in a few minutes. And also making it compatible with Snow Leopard and iOS 4 was no easy feat, but it's really important. And so Arc in a nutshell is a combination of compiler and runtime features that work really well together, enable a really simple model so that you just don't have to worry about this.
So to understand ARC, I think one of the really important things to realize is what ARC is not, right? Because this sets expectations and also kind of tells you why ARC works as well as it does. So ARC is just automated retain and release, right? Because it's automated retain and release, it's not a new memory model. There's not some new thing that's pervasively happening in your application.
ARC is also, again, automating objects, not malloc and free. Well, this doesn't mean that you can't use malloc and free. You're welcome to call malloc. You're welcome to open file descriptors or whatever you want. But if you call malloc, you have to call free. And so ARC will take care of, for example, deallocating all your IVARs and your dealloc method for you, or releasing all your IVARs and dealloc. But if you have a file descriptor, you still have to write a dealloc to close it when your object goes away. And so ARC is taking care of your objects for you, but it's not getting in your way if you want to do other stuff.
And finally, one of the major differences of ARC and other approaches is that this is a compile-time memory management model. This is not a runtime memory management model. If you look at memory management, there's really three different places you can conceivably do it. One is you force a programmer to do it completely manually.
That's what retain-release is. One is you do it completely at runtime with the garbage collector. So now you're trading off runtime performance to scan the heap and pause the application while you're deallocating objects. Well, that's a nice model in some ways, but now you're trading off major performance and predictability. And now your objects get deallocated whenever the collector kicks in, not at a deterministic time when the last reference goes away.
ARC is an amazing solution that's right in the middle. Instead of throwing away runtime performance, we're going to have the compiler do it. This gives you the convenience of not having to worry about memory management without the performance hit of doing it at runtime. So we think it's a really amazing, great answer for Objective-C.
So how does this work? Well, basically you just write Objective-C code. You never call retain or release. You just don't worry about it. You just write the code the natural, clean way that you should be writing it. So here I have a method. This method concatenates a first name and a last name and it returns a full name.
Well, in manual reference counted code, this would be a leak because I'm allocating an object and I'm returning it and full name is not a +1 name for a method. Well, in ARC, it says, "Okay, well, you're returning an object here that's +1 and so it will do the auto-release for you." It's that easy.
If you write this a different way and you use string with format instead of allocating it, then the compiler says, "Hey, well, I don't need to auto-release it because it's already, you know, neutral +0, right?" And it just does the right thing. So you write the code the natural way that you want and then ARC takes care of the memory management for you. It's really that easy.
So, one of the things that, talking to people, I've struggled with is that people say, "Okay, well, where does it insert the retain? "How do I know that it's going to be retained? "How do I know that it's going to do the right thing?" Well, just don't do that, right? First of all, it's really hard because we have some really advanced compiler optimizations that move retains and releases around and get rid of redundant ones and all that kind of stuff, but also because you don't need to do that. Just think about it as you have an object, as long as you have a pointer to that object, it's alive. When the last reference of that object goes away, so does the object. It's that easy.
So, that means that you start thinking about pointers in your application. Think about your object graph. Think about if you have a pointer to an object, that pointer is keeping it alive. That means it's owning that in some sense, right? And so now you start thinking about what that means, and the one thing you have to keep in mind is the possibility of a retain cycle.
and I are here to help you. So, overall, just forget about retain and release. Hopefully, you're okay with that. I don't want to take away your best friend or anything. But you can just leave it behind. So what does this do when you move an application to ARC? I mean, the primary thing that you may be wondering is, OK, I moved my app to ARC. What happens? Well, the most obvious common thing is that your dialog methods just disappear. Tons of code dissolves. All those calls to retain and release go away. Your code becomes much simpler.
Now, there are some cases where ARC requires new code. But these actually are really great things, because if you look at some of the places where ARC requires this, you can see that it's making your code easier to understand. Now, here's a class, an example, and in Manual Reference Counting, I look at this IVAR A and say, "Hey, is this a retained IVAR or not?" And so to find that out, I go look through the implementation, I look for all the uses of A, and I say, okay, well, it's not being retained, so therefore it's an unretained IVR. And because I don't want to have to do the search again, I add a comment.
It says, hey, this is unretained. Well, this is a really fragile thing, because looking at the interface, if you didn't add this comment, you don't know. It's really easy to make a mistake and actually retain it when assigning, and this is why properties came along. This is kind of solving the problem through the language.
ARC takes the same problem and solves it a slightly different way by adding an annotation right onto the IVR. And so we're building it right into the language where you say, hey, this defaults to a retained IVR, but if you want to have a weak IVR or a non-retained IVR of some other sort, you add it right to the code.
This means that looking at the code, it's really obvious how your IVRs are being used, and you can't mess it up, because the compiler takes care of it for you. So we think that these additions are really great. It makes your code much more obvious, and it makes it easier to understand when somebody else comes to your code and needs to fix a bug or add a feature. This is the right thing to do.
So how do we make Arc compatible with existing code? So before the conference, you're happily writing retain-release code, and we can't just say, "Hey, well, your code doesn't compile anymore." Right? You probably would not appreciate that. So to do that, we add a new flag. Arc is a new mode in the compiler that's enabled by passing fobjc_arc, and that means that if you're not passing this flag, which you haven't been, then your application still builds the same way it was, and you know what? Retain-release will always be supported. There's no problem with that, right? If you like manually doing retain-release, you can continue to do that. So Arc only is kicked in when you have an Arc application or when, you know, the flag is being passed.
Now, part of this is that in Xcode 4.2, all the new projects default to Arc. So if you start a new app, it will automatically be Arc. If you have an existing app, then you go through a process called migration to get to Arc, And we'll show you how that works in a bit.
So I talked before about the static analyzer and how the static analyzer is a great tool, but it gets confused. This is completely unacceptable for the compiler. The compiler must be 100% reliable. Now to do this, we have to make an engineering trade-off on how do we make it so the compiler can reason about the code 100% of the time correctly. So the solution to this is that we're actually adding a few new rules that you have to follow in ARC mode that make it 100% reliable.
And at the end of the day, it formalizes best practices Objective-C anyways. And so there's four new rules, and let's walk through those. The first rule is that the compiler is going to be doing the retains and release for you. That means you are not allowed to call retain and release.
If you try, the compiler will tell you you can't do that. Now I'm sorry, I hope you're not sad about this. I mean, it means that wonderful things like this where you're looping over the retain count calling release, you can't do that anymore. I know you're disappointed, right? But the great thing is you don't have to, right? This is a sign that something has gone tragically wrong. This isn't a feature, right? And so, in fact, not calling retain and release is a huge feature.
Now, one other aspect of this is it means you can't implement retain and release yourself. And there have been a few patterns that have needed this. And so to solve that, we've actually added other answers for those patterns. So singletons, for example, one common way to implement a singleton was to implement retain and release for yourself.
Well, you just don't have to do that in ARC. One common reason was for performance because NSObjects retain and release implementation wasn't as fast as it could be. So we heavily optimized NSObjects retain and release performance. So now you don't have to do that, right? And so instead of giving you a way to do this, we've tried to define a way the reasons you wanted to.
The second rule is that you're not allowed to use a retained pointer to an object in a C struct. Now this is a pretty nasty one because it seems like the compiler should be able to make it work. The problem here is that the compiler needs to know when an IVAR or a reference to an object comes and when it goes.
C structs are completely freeform. You can allocate them with malloc and they're filled with garbage. You can pass them off to free and they get deallocated. You can pass them through several levels of function call and then they get freed. Well, the problem with this is that when you do that free, the compiler needs to know to release that IVAR.
And okay, well, you have a pointer to a struct. Is it a pointer to one struct? Is it a pointer to an array? The compiler doesn't know. And so instead of guessing and trying to make it sometimes work, we said we need it to be reliable and so just don't do this. Now, this again is not a huge problem. You shouldn't be using C structs anyways generally.
You should use objects. So best practice in Objective-C is and always has been to use objects. And so if you're using these kinds of things, objects are a good answer. If this doesn't work for you because you have like some dense hash table or something like that, you can use void star.
So this is an example of the kinds of tradeoffs we've made so that it makes your code easier to reason about for the compiler. It makes it really clear what's happening in your application. So another issue is casting. So we do not automate objects. We do not automate file descriptors or core foundation or anything other than objects.
So if you do a cast, the compiler has to know if you're casting to a CFRF or a void star, is it retained or not, right? And so if you try to do a bear cast like one of these guys where you're casting between a CFRF and an object pointer, then the compiler will tell you, hey, you can't do that.
Well, what's the solution? The solution is to tell the compiler what it needs to know. And so we're adding a few new modifiers to these casts. So you say, hey, this is a transferring cast. This gives the retain count from the CF object to the-- to arc to manage.
And I'm not going to go into these in detail, but it's very straightforward. We're also adding new CF API. So you can say, I want to do a CF bridging retain, things like that. And so this gives you new patterns so you can use. And the idea here, again, is that we don't want to be super intrusive in the code, but the compiler has to know what's going on. Otherwise, you get a fragile model that's not reliable. And the compiler has to be reliable.
So the fourth thing is, again, a really minor thing. You can't use the NSAutoReleasePool class. Well, NSAutoReleasePool is this interesting class where it's never really been an object anyways. I mean, you allocate it with alloc and it, but you can't actually retain it. If you retain it, it throws an exception.
So the problem with NSAutoReleasePool from the compiler's perspective is it's not necessarily properly scoped. It's not necessarily properly balanced. You can do all kinds of weird things. You can do an alloc and then sometime later do an init, for example. And the compiler has to reason about references that live across that pool.
Well, to do this, we build it directly right into the language. There's a new @AutoReleasePool statement. It works just like @Synchronize, where on entry it pushes and on exit it pops. Really straightforward, syntactically, also much more elegant. And this defines away the problem and makes the model 100% reliable. Now, through these four simple rules, we get a really great model where we think that Objective-C code is easier to read and the compiler will always do the right thing. And that's really the power of ARC.
So let's talk about one more minor issue, which ARC-- also, @AutoReleasePool works everywhere, not just in ARC. So one more minor issue, which is retain cycles. Now, ARC is just automating retain and release. So the compiler is doing retain and release for you. Now, unfortunately, the compiler is not doing that.
The compiler does not know the bigger picture of how the objects in your application work together. So because of this and because it's just retain and release, retain cycles, which have always-- the possibility of retain cycles always existed, can still happen in ARC. So to give you an example of this, say you have some objects. Each of these objects get a retain count. So there's three pointers-- the first one, two pointers, the second one, et cetera. Now, if you drop a reference to an object in a cycle, the retain count drops, but it doesn't drop to zero.
Thank you. Thank you. So, we're going to talk about the source of crashes, right? Because an assigned property or non-retained IVAR is, you know, a potentially dangling pointer. It's pointing to an object, the object gets deallocated, you know, you have a pointer to that old object, and if you dereference it, well, it won't crash for you, it'll crash for your customers instead, right? This is the insidious kinds of problems that memory management bugs lead to. And so we want to solve this as well. So in addition to automating memory management, we're introducing a new kind of pointer called a zeroing weak pointer.
[Transcript missing]
So, we're not going to go in depth into blocks here, but if you're interested, there was a talk earlier this week, and the Objective-C advancements in-depth talk later today, we'll talk about this in more detail. So, ARC now is, I think, a really fantastic model for Objective-C.
You may be wondering, can you use it? Well, the first thing to keep in mind is that there is no runtime that's kicking in and stopping your application to collect memory. So, first of all, you don't have any of those kinds of problems that cause your application to stutter in unpredictable ways because the collector kicks in. So, ARC doesn't require that.
What about performance elsewhere? Well, we really wanted to take away any fear that ARC was going to cost you performance. So, we went through and optimized three different subsystems of the Objective-C runtime and the frameworks. One is the retain-release implementation in NSObject, one is the new auto-release pool syntax, and one is Objective-C message send itself.
And so, in iOS 5 and in Lion, now, you'll see the NSObject is much faster, two and a half times faster than it was before. This is a major performance win, and this defines away many reasons for wanting to implement your own custom retain-release. Also, the new auto-release pool syntax, it doesn't have to allocate an object, it's just bumping a pointer. It's much faster than NS auto-release pool used to be.
So, it's six times faster. This means if you did crazy things where you're saying, "Okay, well, in my loop, only drain it some time," you can just use auto-release pool and not worry about it. It's much simpler, you shouldn't have to worry about this kind of stuff, right? We've even sped up Objective-C message send. Now, I admit, this is not a huge speed up. We shaved three cycles off it.
It turns out Objective-C message send is already highly performance optimized, but it's really core part of Objective-C, and it turns out three cycles on something that's not 12 cycles is a huge speed up, right? And so, we think that these substantial changes in the performance of the object-c message send, will really help with performance of the system overall and with Rcaps in particular.
So, a final optimization, which we'll describe again in more detail in the in-depth talk, is that the compiler and runtime were co-designed so they could do really impressive optimizations. One example of this is with auto-release return values. So, in many cases, when an ARC method calls some other code which ends up doing an auto-release and returning an auto-release object, that object never gets put in an auto-release pool.
It gets magically warped back to ARC, and it can just use it directly, which saves a retain, it saves an auto-release, that object doesn't get put in the auto-release pool at all, making this dramatically faster, up to 20 times faster. This means atomic properties, common getters, a lot of things are much faster, which doesn't work in non-ARC mode, but this is one of the major advantages of co-designing the language with the runtime. It's fantastic. So, we'll explain more about how this works in other talks.
But this gives you a really high-level idea of what ARC's all about. It's about synthesizing retain-release, so the compiler does it, and you don't have to. We add a few new runtime things, like zero-week pointers, which help define away a common cause of crashes in applications. And to make it reliable, there's new rules that you can follow. They're not onerous. They're very simple. And the result of this is that you get a great model where your applications generally just work. You don't have to worry about retain-release.
In many cases, they're even faster than they were before. And, you know, it's just a better place to be. So, to give you an idea of how this actually works, details that the compiler thinks about and you don't have to worry about, I'd like to invite John McCall up to tell you about what goes into the compiler and how the compiler thinks about your code. John? Thanks, Chris.
Thanks, Chris. My name is John McCall. I work on the compiler team on the LLVM compiler, most recently on automated reference counting. I want to give you an idea of how ARC does all of this, mostly so that you can have a lot more confidence in the compiler, but also so that when you understand how you might need to make -- if you need to make -- if you still -- if you can't convert completely over to Automated Reference Counting, if you need to have some of your stuff still manual, retain, and release, then you kind of need to know this so that you understand how that code needs to interoperate with ARC.
So, memory management is hard. Chris covered this great. I don't want to belabor this. But there's a lot of rules and conventions, some of which I'm going to go over today. It's a lot of -- it's a huge hurdle for coming to our platform. It requires constant attention for existing developers, and it really requires perfection. And to really bring that home, I want to work through an example of what I consider to be a great application as a compiler writer, which is a stack.
Now, this is a very simple stack. We've got a -- it's backed by an NSMutable array. We allocate that in the init. We push it. When we want to push something onto the stack, we just add it to the end. When we want to pop something from the stack, we just pull it off and then actually remove that last thing.
This is a very natural way of coding, and it makes sense to write code like this. But if you're an experienced Objective-C developer, you're already looking at this and probably identifying three or four different major memory problems in this, right? So the first one of those is that this array is auto-released.
So when we're storing it in, if this stack needs to persist across popping an auto-release pool, like between UI events, this array is just going to become magically a dangling reference. All of this is when you're compiling in manual retain and release mode, of course. So in order to fix that in manual retain and release, we have to actually insert this retain.
That causes a problem because now that retain is not being balanced out by this code anywhere. So we end up needing to add this totally new method that we didn't, you know, think that we needed to write before, called Dialog, which will actually release that thing. And as part of doing that, you have to also remember to call superDialog. And of course, none of you would ever make this mistake, but it does happen.
That fixes the problem with this IVAR, but then we have this other problem with the pop method, where we pull something out of the array, and then we remove the array's reference to it. And if that was the last reference to that, if that was the last retained count of that, This is going to be a dangling reference immediately, and we're going to end up returning that and probably crashing our caller.
And the really frustrating thing about that is that sometimes it'll work, because sometimes the objects that I've stored in here are retained elsewhere, and sometimes it'll work because I'm accessing a reference that's gone away, but maybe nothing else is allocated at that same address, and it still kind of works.
And sometimes it'll look like it works for even more elaborate reasons, and it's frustrating. So the way that you have to fix that, again, in manual mode, is that you actually have to do a retain here, but that's a problem, because that's going to end up giving us a leak, because our callers aren't going to expect to have to do a release here, so instead we have to do an auto-release at the last second.
This is a very small, kind of contrived example, but it's the sort of thing that you have to think about all the time when you're doing manual retain and release code. Right? You shouldn't need to write about that. You shouldn't need to worry about that. It should just work. ARC makes this just work. I'll go ahead and blow my punchline. This is a -- that code that you just saw works perfectly in ARC. It works exactly the same way that we had to write -- that we wrote out manually.
Right? So the idea here is that in ARC, you can just write code naturally the way that you want to do. Right? You don't have to worry about retains and releases. The compiler just does it for you. All you need to worry about is breaking cycles when necessary in your object graph. Otherwise, you can focus on the things that are important to you.
So, how does Arc actually do this? Well, mostly it's just automating existing practice. It's automating what you would have had to do in manual retain and release mode. It does that by applying very local rules. So, on the scope of an individual operation, it's always balancing out retains and releases. Individual stores or calls or things like that.
It's trying to guarantee local correctness, which then ends up creating a whole lot of work for itself, and then we have an extremely powerful optimizer to clean that up and make it as fast as possible. But that's the high-level view of how Arc works. Now I said it applies local rules. There are really two classes of rules here. The first class is the rules for having a pointer to something in memory, right? What I'm going to call a variable.
Like this is any sort of -- but this is really any sort of place where we're storing a pointer in memory. Like a global variable or an instance variable or something like that. Anything -- any sort of variable like this that's an Objective-C pointer type or a block pointer type ends up being managed by Arc.
We looked at all the code that's out there right now when we were designing Arc, and we identified three different common patterns for three different ways in which we could automate how people keep references in memory. The most important one of those by far is strong references. So this is what a strong reference is, is it maintains the invariant that whatever it's holding onto is retained by that variable. In Arc, this is the default variable.
So when you write something like NSString name here, you're actually going to get what the compiler interprets this as is strong NSString name. And it really works a lot like a retained property. What do I mean by that? Well, when we're creating a variable, and this is any sort of variable.
Like even a local variable on the stack. The first thing that we always do is we always initialize it to nil. This is important for the code that we're going to generate, but it's also really, really crucial for the safety of your code. This by itself is going to save you a huge amount of annoying debugging effort.
Because you don't have to worry about random little bits of code that you forgot to initialize and therefore get filled with total trash from the stack, and then your program has all sorts of errors. All sorts of crazy errors in it that only turn up far downstream of the original bug. So it's a lot safer.
This is a huge -- this by itself is a huge improvement in your life as an Objective-C programmer. The second important thing is when you're destroying a variable. Like for a local variable, that means when it goes out of scope. For an instance variable, that means when it's being deallocated. When you're destroying a strong reference, you just take the current value in it and you release it.
Right? Maintaining the invariant that -- You know, you have this invariant that whatever is in there is retained. When the variable goes away, you have to release it. This happens for everything. Because we do this even for IVARs, that's the most common reason for you to need a dialog. Usually, you will almost never need to write a dialog anymore.
The last two rules for dealing with a variable like this are when you're reading or writing from it. Now, when you're reading, there's nothing special going on at all. For writes, again, we're maintaining that invariant. So that means you take the new value that you're doing the assignment, you retain that, Remember the old value, do the store, and then release the old value afterwards. Right? Again, maintaining that invariant. And that's it. That is most of how Arc actually keeps things working. Right? Just applying those simple rules methodically over and over again throughout your code.
So the second set of semantics that we found for local variables, variables in general, are auto-releasing references. This is really just solidifying the behavior of out parameters. So this is just describing what these do. It's not really useful for general use, so I'm going to gloss over this. Basically, it just means you retain an auto-release when you write into it. And otherwise, it doesn't have any extra rules.
The third kind-- is unsafe references. So this is a lot, the behavior a lot like an assigned property today. In fact, it's exactly like an assigned property. So when you're loading from it, it's just a load. When you're storing from it, it's just a store. Totally unmanaged by the compiler. That means there's no extra logic for it. It also means there's no restrictions. So you can put it in a stack or on a stack like that. Or in a struct. You can have one of these in a struct.
Now, because there's no extra logic here, they really are unsafe, right? You can end up, these can easily become dangling references. So we said, "All right, well those are the three kinds "of variables that we see in code today, "but this one is really bad, right? "This is not how you should be, you know..." Chris mentioned that there's all these potential problems with it.
We think we can do better. We can give you a better tool for breaking routine cycles than just these unsafe references. So instead we added weak references to Arc, right? They behave exactly like assigned references, except for this magical thing where they become nil as soon as the objects that they're pointing to starts to be deallocated.
Now, the way that works is that the compiler inserts all these crazy and I have a few crazy calls whenever you're manipulating one of them. If you really want to know a lot more about these, feel free to come to the Objective-C talk. I encourage you to. That's 11:30 today. But for the most part, you don't need to worry about how it's actually done. Just think about it as behaving like a weak reference. It just drops down to nil magically when the object goes away.
So the second set of rules are for transfers in and out of the control of the local function. For the most part, that means a return value. Is a return value transferring ownership of a retained count to ARC or not? That's really going to be decided in Cocoa by the kind of call that you're doing, the name of the call.
So we have this concept called a method family. This is really just the existing Cocoa naming convention. So if you have a method whose, and the first word in that selector, and the selector for that method starts with one of these magic words, then it just, then it returns a plus one object that Arc has to balance out. Everything else doesn't. Any sort of C function called the default behavior, it's just going to return an unmanaged, you know, a plus zero. We have to do retains and releases ourselves.
So how does that -- how do those rules end up translating into code that the compiler does for you? Well, a normal return where there's no transfer, you just -- at the point of return, you retain immediately, pop out through all the scopes of the local variables that you're in, and then auto-release at the very last moment. And Chris mentioned that that auto-release is often going to be optimized. It will just turn into, you know, it'll be a transfer of that retain count directly to the caller.
For a retained return, like a method, same method, just called new instead, that's going to pass back ownership. So it basically works exactly the same way, except without the auto-release at the end. And then on the other side of that, where we're accepting it, We just take the value back, use it however we're going to use it, and then release it sometime later at the end of the statement when we're sure we're not using it anymore. So that's it. These are the rules that Arc uses to make your code safer. So let's go back to our example.
Here we have this init method. The important thing to look at here is that we've got an IVAR. We have a strong IVAR now. And we have this assignment to it. So when we expand out that assignment, again, like I mentioned before, we're going to retain the new value, release the old value. And then since we have a strong IVAR, we're automatically synthesizing this dialog. No complexity here. Just straightforward making it work.
For this pop method, there's two interesting things going on. The first is that we have this local variable, which we have this assignment to, and then it goes out of scope. And the second thing is that we've got a return. So let's expand that return first. Like I said, you do a return by retaining, popping out through all the scopes that you're inside, and auto-releasing. So we pop out through all the scopes. So let's expand the behavior for this local variable.
We do a retain as part of initializing it, and then when it goes out of scope, we release. Now you can see already That there's this totally unnecessary retain and release pair. This is the most trivial sort of thing that our optimizer is capable of deleting. It's much more sophisticated, but it can certainly zap this case. And we end up with exactly the code that we would have written manually. It's the local rules that make this work, right? You have local rules making individual operations safe, and then you have a global optimizer that's making sure that doesn't cause you any extra effort.
The point is that you can just write code like this. That natural code that we had in the original example, it just works under Arc because of this steady application of local rules. So to put it together for you, ARC is there to follow the conventions. Compilers are great at following conventions. We have to follow conventions for everything, right? I get up in the morning to make the compiler follow conventions.
Let me do that, right? I can make the compiler follow the rules much more precisely than you can, right? It's really simple to just take these rules, apply it over and over again, and end up with code that just works. What I can't do is make your apps great. I can't make your customers happy.
So what I can do is let you do that so that you don't have to think about retains and releases anymore. And that's the value of Arc. So I want to call up Patrick Beard to talk about how you can convert all of your existing code over to Arc and therefore take advantage of all this stuff for what you're already doing.
Thanks, John. So as John said, lots of rules. The compiler is going to think about them real hard so you don't have to. So I'm going to talk about how you migrate your code, what you're going to see while you're migrating, how you actually make this stuff work, how you put it on the on our operating systems. And then I'll do a quick demo about some real code. So the steps. Well, first, to get into this, you have to use our compiler, LLVM version 3.
You'll use a command in Xcode which will change your code, it'll analyze your code. When it's satisfied and you have made all the changes that it has identified that you need to make, then it will modify the code in place, present you with the diffs, and away you go.
So here's what it looks like in Xcode. It's a refactoring. This is what it does. It goes through all your code, finds all the problems, removes, retains, release, auto-release, gets rid of auto-release pools, uses our new language construct. and it will replace your assigned properties wherever possible with weak properties.
So this runs in two phases. Analysis, where you find the problems, treat them as compilers, fix them, and then analyze again. Then there's conversion. Changes the code. If you're happy with the changes it makes, compiles it, turns on the option, opts in, and your code now works and runs in Arc, hopefully.
So here's some of the problems you might find when doing this. Here's a case where you might have this in your code. You're calling a method that the compiler has no idea
[Transcript missing]
Another issue would be fancy uses of auto-release pools, where you want to go through a loop, but you don't want to drain the pool every time. Well, that's not going to work anymore.
For this code to migrate, it has to become block-structured. So that's what this compile error is telling us, that you can't use this class anymore, but I don't know how to fix it. Please fix it for me. So, a simple way to fix it, just go ahead and use the addAutoReleasePool construct.
And auto-release pools are about six times faster. So the empty case, where there's nothing in the pool, is just really fast. We don't allocate any objects, we just bump some pointers. Another issue if you've done any C++ programming, this is going to be old news to you, if you declare a variable in a case statement, cases that follow that are going to generate this error. It just basically means add a curly.
Very simple. Now let's talk about singletons. There's actually quite a number of recommended recipes people write about online in various places, various forums, maybe even some of our own, that suggest ways of writing singletons that are just not necessary anymore under Arc. You might have found examples like this where you override, retain, and release just to more or less prevent errors where you're using a singleton and you don't want to accidentally over-release it and have the singleton go away. When you're writing code in Arc, Arc takes care of this for you. This won't even compile.
So you don't need to do this anymore. Trust us. Another method that people override sometimes is allocWithZone. Purpose here being to ensure there's only one true instance that rules them all. Well, you don't need to do that either. And there's actually a subtle bug that can occur if you do this.
If people call allocInit on your class and they don't use your singleton accessor method, the init method can get called multiple times on the singleton object. So you have to be really careful about writing an init method that handles that. So just don't do it. Writing singletons, just use simple accessor methods.
If you want your singleton allocation to be thread safe so that, I don't know, you get actually a correctly allocated object if you're using it for multiple threads, use dispatch once. That just solves the problem very neatly. One other thing, class objects, well they're objects. They are singletons. So if possible, in this example, we could just use a static global variable. and write class methods to do the exact same thing. We don't even need to access or allocate an instance. So consider this pattern.
As we've said, assigned properties, which are commonly used for delegates, they're going to become weak. Enough said about that topic. It's very simple. The migration tool will do that for you automatically. All right, now where can we use this? Well, we're introducing this with Lion and iOS 5. But we provide a compatibility library so you can actually deploy your code and you don't have to make the decision about using ARC. You can just use it and it will work on Snow Leopard and iOS 4.
So how does it work? Well, you use the deployment target. A very natural way of telling the compiler this is what you're doing. Choose an earlier deployment target: 10.6, iOS 4. The linker will automatically be given a library that is linked into your program that runs at full speed if you're running on iOS 5 or 10.7, and it goes into a compatibility mode if it finds itself on an earlier operating system. So your code is compatible.
We give you something, we've got to take something away though. The classes that we have in our frameworks and our runtime system are where weak references are supported. So on these earlier systems, weak references are not available. So you have to use this unsafe and retained. The migration tool will actually do this for you as well. And the compiler won't let you accidentally use a weak reference if you're deploying to earlier systems. It'll just become a compiler. So it's convenient. You'll always know what you're doing. So let's do a demo.
So this is an open source application. It's called WikiHow. It's a front end to a website. I've prepared this so it should fire right up. It's pre-built, not for Arc. So let's see how this converts. So right away, it shows us that our main routine has a problem with its auto-release pool. This is actually an instance of that use of an unstructured auto-release pool.
So we have to hoist this variable declaration before it. A way to think about this is think of auto-release pulls as curly braces, as scopes. So by moving this now, this variable is correctly scoped and we'll be able to write our code this way. Another issue is this undeclared initWithIdentifier method. We can use our indexer to find it. copy it and add it to our class declaration, which is missing.
And as I said, case statements, we're declaring variables here, so fix that by adding a curly. Oh yeah, somebody was getting fancy with their singletons. So we just tossed that out. So mostly I've made a few small edits. So we hit convert again. Crossing fingers, and it comes up, it's happy. So we hit the next button.
Now what it's doing is generating a snapshot so it can show us a diff. And here's our diff viewer. shows us all the changes it's made. Take a look at main, simplified it, a lot easier to read. Here's a case where we have automatically turned assigned properties into weak.
And just for fun, at the bottom of this we see a dialog method just got nuked. Kind of nice. So you'll see a lot of that when you look through this code. And a proof of the pudding, does it run? It ran before. There it is, running with Arc. and if you want to learn how to arc weld, you can use this tool for that. So, thanks a lot.
Thanks, Patrick. So we tried to really define a great set of tools that work really well together to make ARC real and get you to ARC. So let's come back to the evolution of Objective-C. We talked about automation. We're really raising the bar for Objective-C and making it easier and more natural to write code that just works. We think that Arc is the latest great step towards that goal.
So if you want more details about ARC and about other improvements to Objective-C that have nothing to do with ARC, we really encourage you to come to the Objective-C Advancements talk later today. And there are a lot of other improvements. We'll talk more about how ARC the runtime works, talk about performance optimizations, and also have some Q&A about ARC and Objective-C.
So now if you'd like more information about ARC and the improvements that are coming out, please contact Michael Jurowicz, our Developer Tools Evangelist, and we have a really fantastic Programming with ARC release note on the attendee portal for WWDC. This document describes more details than you could ever want to know about ARC, I think, including some of the enhancements we're making that will be coming out in the next seed.
If you have questions, feel free to contact us at the Apple Developer Forum. So, there are a couple of related sessions. Earlier this week, there are several compiler sessions about the LLVM compiler and how to move to the LLVM compiler. If you're already using GCC, or if you are currently using GCC, we strongly recommend you move forward. ARC is only available in the Apple LLVM Compiler 3. So with that, I'd like to thank you for coming. Have a great afternoon.