Developer Tools • iOS, OS X • 39:59
Automatic Reference Counting (ARC) dramatically simplifies memory management in Objective-C. Learn how to move your project to LLVM and ARC so you can write less code and give your application great performance. A must-attend session for developers of all skill levels, on both OS X and iOS.
Speakers: Malcolm Crawford, Dave Zarzycki
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.
Welcome to Adopting Automatic Reference Counting or ARC. I'm Malcolm Crawford. I work in developer publications. This morning, two main sections. I'm going to talk through the first sections on manual memory management and then what is ARC. And then Dave Zed will talk in more detail about how it works and how you switch across to using ARC. So, to turn first of all to manual memory management, we've used a mechanism for doing memory management for the better part of two decades now. We're looking to change that, so clearly there has to be some sort of a problem here.
Why are we addressing this? Well, it turns out that there are problems with memory management. It turns out in particular that memory management is the number one reason for apps crashing and that ultimately leads then to the number one reason for apps being rejected from the store. So anything that we can do to prevent problems with memory management is going to be welcome. At least to the question though, What were the problems? Why do people have problems with traditional Objective-C memory management? After all, the rules, for example, were pretty straightforward.
Thank you! However, there were details, lots of details. The simplest among these, you had to think about the naming conventions and adhere to those and possibly you had to think about auto release pools, blocks came in, you had to worry about whether the method that you used was return an auto released object and then think about not calling it auto released, it was an object you didn't own, and then maybe you wanted to use alloc init instead and all of these sum together then to lead to effectively cognitive overload or at the very least a set of things to think about that weren't directly related to your primary goal of implementing interesting code. What made it worse was you needed to do it right all of the time, so you never were allowed to forget the rules or make a mistake. And it turns out that with the best people in the world, this isn't what people are best at.
[Transcript missing]
The other observation that was part of the development of the analyzer was the realization that Cocoa has strong patterns and conventions, and in particular the naming conventions. So, stepping back again to the analyzer, if we've got a tool that's able to figure out when you're following the rules and if you're breaking them and so on, perhaps we can actually invert things a little bit. So, ARC formalizes conventions for us and then automates the rules so that you don't have to. And in particular, this then is what compilers are best at, following and adhering to rules.
It also, in general, is a sort of big picture, a slide that you might have seen before and probably see again during the conference, fits in with a general evolution of Objective-C. Starting from C, we're gradually making the language and the environment safer and simpler through automating aspects of it, and now ARC is the latest addition to that.
So then, what is ARC? In essence, as you should have really got already, ARC provides automatic memory management, but I'll emphasize a second part, for Objective-C objects. So it's just for Objective-C. ARC obeys and enforces the existing conventions for you. It's important that it obeys and adheres to the existing conventions because this is what then makes ARC compatible with existing code that you already have and with frameworks that we already have that don't necessarily yet use ARC.
ARC introduces a couple of useful features, in particular, weak references. I'll talk a little bit more about weak references later. And then takes advantage of some performance optimizations so that you don't see any performance regressions. Conversely, a couple of things that ARC is not, first of all, there's a corollary of the first, it's not a new runtime memory model. It doesn't automate C structures and so on. So you still have to use Malecon-free and do memory management for core foundation style objects yourself. It's also not garbage collection. So it doesn't have the problems that garbage collection does that are particularly problematic on devices like this.
Fundamentally, it works simply by adding the appropriate retain, release, auto-release, and so on methods for you that you would have otherwise had to have written. So, quick pop quiz. I trust everyone will instantly see the problem with this method implementation. It doesn't adhere to the proper naming conventions. ARC, when you compile this code, will notice and do the right thing for you. So it'll add the code that you should have written yourself. Conversely, if you'd implemented Greeting this way, ARC will again realize and not make any changes. The code's already correct.
As a tangent to this, a useful aspect of ARC is that then both of the possible ways of implementing this method have, in effect, the same cognitive load. You no longer have to think about, "Do I use alloc and init with format, or do I use string with format?" You simply use the method that you are most comfortable with, and don't worry about the memory management aspects.
Hopefully now that's sufficiently enthused you to switch it on, so how do you switch it on? There's a project setting in Xcode. That's the fundamental thing that switches the compiler flag for you. If you've got... A new project, all projects now default to using ARC, so there's no additional work that you have to do. If you have an existing project that you want to migrate to ARC, Then you do actually have to use a migration tool. And all of these then Dave is going to talk about in more detail in the next section.
Generally, what you should see when you switch across to ARC is a lot of code simply goes away. So, typically, for example, a dialog method just doesn't exist anymore. Other things become simpler, so dealing with the memory management semantics of blocks. leaves you just with the code that you wanted to write, rather than thinking about having to copy and then auto-release. Sorry about that slide.
An issue that does come up though is that, having talked about the static analyzer earlier, the static analyzer depends on heuristics to reason about the Objective-C rules of memory management conventions and so on, and isn't necessarily deterministic. You can't have a compiler that isn't deterministic. So, in order to make sure we get consistent and reliable results from ARC, from the general perspective, we had to formalize and automate the best practice, but we're also introducing four new rules.
That might sound a little bit like a step backwards, but an important aspect here is because we're formalizing any particular automating the rules here, the compiler can actually enforce the rules. So even if you forget about something somewhere online, the compiler is there to remind you and compile time rather than you discovering through a crash log later. So, let's look at the rules. The first one I think I would call a feature rather than a new rule to think about. You can't now use retain, release, auto-release, and so on. The benefit here is that you can't write code like this.
So we generally regard this as being a benefit. Instead of having to deal about memory management methods, the compiler takes care of it for you. It does mean that there are a couple of other new practices to introduce, particularly, for example, the way you deal with singletons, but we've got straightforward new patterns for those. So, rule two.
You can't use references to objects in C structs. The compiler again has got to be able to reason about the lifetime of objects and that's rather more difficult in a structure. So rather than using structures now, we're asking you to adhere to what has been common best practice anyway and to use Objective-C objects. Not only is this best practice, but then you get a better tool support and so on. So where you might have used a struct before with a reference to an Objective-C object, now simply create a new class to represent that.
[Transcript missing]
Objective-C objects and core foundation style objects. We've got new casts, new keywords to do the casting for you. A couple of new ones introduced with Mountain Lion and the old style ones from Lion and before. Better still, actually, is if you can possibly avoid using core foundation style objects, avoid using them in the first place. An example here might be CFUUID. If you're currently using CFUUID, look towards using NSUUID instead.
Fourth rule, no auto-release pools. So this may not affect that many people anyway, but if you happen to be using auto-release pools, again, the compiler can't do very much with those, so we now have an at auto-release pool block instead. An advantage here is that this works even without ARC. So you'll actually see in any new project that you create that don't use ARC, you'll still see at auto-release pool. And it turns out this is actually much more efficient.
From your perspective as a developer, how then do you think about ARC? What are the considerations that you have if you're not thinking about memory management? Instead of memory management, you now want to think about object ownership. You need to think about the references between objects and how you define those. In particular, if you want to assure the longevity of an object, you need to have a strong reference to it. A strong reference to an object will keep that object alive.
As soon as there are no strong references to an object, it'll be deallocated. So fundamentally, you have to start thinking about your application, your program from a higher level in terms of your object graph and the relationships between the objects. And as a corollary, stop thinking then about retain release and auto release.
One of the aspects of thinking about your object graph then is if strong references keep an object alive, what happens if I want to reference to an object that doesn't keep it alive? This is where weak references come in. A weak reference is a reference to an object that indicates non-ownership. It doesn't keep the referenced object alive. Moreover, unlike existing just simple pointers, a weak reference automatically drops to nil when the object that it points to is deallocated.
Week references supported in ARC using properties with the week property attribute and underscore underscore week as a variable keyword. Week references come into play in particular when you're thinking about the possibility of cycles in your object graph. Just like standard traditional memory management, ARC doesn't give you any protection against what we used to call retained cycles and what we're now going to call strong reference cycles. So if you have a strong reference cycle, you will still get leaks.
The good news here at least is that the patterns that you've used up until now to prevent these sorts of things still work. So setting property values to nil and so on will still apply. Having a strong reference from a parent to a child, but weak references from child to parent and so on will all help you avoid reference cycles in your object graph. Judicious use finally then of weak references are going to be a fundamental tool to combat these.
Another question that comes to mind is obviously how well does this perform? Am I going to see performance degradation as a result of switching across to ARC? Good news here is, no, you generally should not. Indeed, what we've usually seen is that the peak high watermark of an application's memory use actually decreases using ARC. Moreover, you don't have the sorts of issues that you might have with a garbage collected system.
So generally, no problems with performance. So in summary, ARC then provides you with a simplified model for memory management that should lead to you writing less code and be able to think about the interesting code that you actually want to write that differentiates your application rather than the memory management semantics, which ultimately leads to more stable, better-performance applications. So, you should use it. And with that then, I'll hand across to Dave.
Thanks, Malcolm. So how does Arc actually work? So as Malcolm showed, memory management is hard. There are lots of rules and conventions, and it's a high hurdle for new developers. And we really want everybody to be able to come and write great apps for our platforms. And it's a constant attention to detail problem that you'd rather not be spending your time on. And it requires perfection, as Malcolm described.
So let's show you why it can be hard. Let's say you were creating a RPN calculator where you can say like five enter, four plus, and get a result. You would need to implement a stack. So here's a stack. Now, there are lots of bugs here. Let's start going through them. This array, if we don't retain it, it's gonna leak because class methods in our conventions return auto-released, so we need to retain this.
[Transcript missing]
What else are we missing? Well, now that we've balanced this out, we need to deal with this problem. A removeLast object might have side effects. This might invalidate X. So how do we work around this? Well, we add a retain. And we add an auto-release. So now any side effects that removeLast object has will not prevent this object from being prematurely deallocated. So that's it. So that's our stack. So ARC makes this easy. You can write code naturally. And you can break cycles easily when necessary. You can stop worrying about the individual retains. And you can write great apps.
So what we're going to talk about next is how ARC works. It'll be automating what you're doing anyway. It applies local rules, both to local variables and to return values, and we'll discuss those in that order. And it guarantees local correctness, and then we optimize after we've applied the local rules. So what does this look like? Let's talk about variables first. Let's talk about Objective-C objects and block pointers. This includes local objects, global objects, parameters, instance variables, all variables. There are four different kinds of ownership.
So as Malcolm talked about, we have strong references. This is the default. In fact, if you write this, the compiler just assumes you wrote this. That's what the desired effect is. It's the default. It is like a retained property, where as long as this variable is set, it's retained. If it's set to nil, then it releases the previous value. Very simple rule.
Now, when you create a variable, let's say you just typed in a string name, what happens? Well, under ARC, we need to have local simple rules. This variable is now initialized to nil, so you don't have to type that anymore. It's safer, it's simpler, it makes rules easy to implement.
So, destroying a variable. Similarly, just like C, we have local scoping rules. This if block has two curly braces and that name variable is only valid within that scope. At the end of that scope, it's going to be released, just like that. This is what ARC does for you. And this is true for Ivar's and the DLX method because that's the end of the scope of your object when it's done.
So reading and writing, let's say you were to assign a variable, a new one variable to another. What does the compiler do? Well, it retains the new one, takes the old one, assigns the object, and then it releases the previous value. Again, really simple rules that the compiler can automate and enforce.
As for auto-releasing, let's say there's a very common idiom in Cocoa code. We have an out parameter for an NSError. We check to see whether it's valid, and if it's valid, then we assign to it. The Cocoa convention is that this is auto-released. And in fact, under ARC, this is what the compiler automates. So we recognize this pattern and we do the right thing. Having said this, this is not a general pattern, so you probably won't run across it much in your own code.
Finally, unsafe references. They're like a traditional variable, they're completely unmanaged by the compiler. They're not initialized, there's no extra logic, there's no restrictions. So you can put them in C structs. And in fact, that's exactly where you'd want to use them. So for example, if you had a table at Global Scope that you wanted to index into with a number and then get an NSString out of it, you would declare that NSString is unsafe unretained and then be done with it. And as long as you never update that table, you're going to be done with it. table, the right thing happens.
Finally, weak references. This is a great new concept with ARC, and this allows for a safe and simple way for us to model how to deal with things that we don't want to own, but we want to be able to reach. So let's say you had an NSString.
And you wanted to reference it weekly, so you could add that variable. What the compiler will omit is a store barrier. It'll take the object and then ask the runtime to update the week variable. And then if we were to message it, the compiler will omit a readweek to see if the object is available or not. And as hopefully you all know, it's okay in Objective-C to message nil. So if readweek returns nil, this will be fine. Similarly, if we wanted to zero it out, we could just write nil out to the week variable and the right thing will happen.
And the cool thing about Weak is, as soon as the object starts destruction, the variable becomes nil to any other clients trying to read it. So that's it. That's the rules for local variables. The rules for return values now are much like you've already done with existing programming. And the questions are, does this transfer ownership? Are they returned retained? Are we allocating new objects, for example? And it's similar to transferring into and out of ARC like Malcolm described. And we do this by looking at the method family name.
The Cocoa convention is that the first word of the object tells you what the semantics are. And the first word is the lowercase part up until the capital part. So initWith is an initializer, initialize is your own thing. So the words that we care about are alloc, copy, init, mutable copy, and new. These transfer ownership, i.e. they return retained or plus one, and everything else does not.
So if you have a method named copyMachine, guess what? It returns plus one. So for normal return values, let's say we had this basic getter to return something called serial. There's no transfer of ownership. What ARC does is it does what you should have been doing all along, which is you retain the object and return it auto-released.
This is the Cocoa convention, and now ARC makes the natural thing do the right thing. Similarly, if you said new serial and now we're conforming to the Cocoa convention for returning plus one, ARC will know that we need to pass this back retained, so it'll generate a retain and return the value.
So similarly, if on the caller side, we need the ARC will automate based on the name. So in this particular case, we're calling new serial and we're just having a temporary variable that's being passed straight to NSLog. Well, ARC's going to automate this and actually create a temporary variable, store it, pass the value to NSLog, and then when we're done, it'll call release. I hope you agree that this is much simpler and a powerful automation so you can write natural code.
So let's go back to that stack and talk about how ARC applies these local rules and then optimizes. So here in our NIT, ARC is actually going to do this. It's going to create a -- it's going to look at the old variable, cache it, retain the incoming variable, and release the previous one. Similarly, it's going to generate a dialog method to balance out the property in the class. And ARC will automate the call to superdialog, which is very important so that way our superclass can clean up its properties and instance variables.
Similarly, let's look at what POP does. What does ARC do? Well, we have two things to do, in fact. We have to follow the return value convention. We have to retain the previous value -- or, sorry, we have to retain what we're returning and auto-release it. So, great. We've followed this rule.
We also need to deal with side effects, so we need to retain the value X and then release it when we're done with it. Because, as we talked about earlier, removeLastObject might have side effects. One of the side effects might be a release of the object, so we need to retain it locally.
When you combine these two, you notice that we have a redundant retain and release. Well, guess what? We've added an ARC optimizer to the compiler and we can delete those, thus generating the code you would have written by hand, the optimal code, by applying local rules and then optimizing.
So in the end, we can now write natural code. We like NSMutableArray because it's nice and short, we only have one set of square brackets, and ARC does the right thing. We don't need to worry about side effects, which, to be the truth of it, most of us forget about them a lot of the time, and our pop method just works. And most of all, we'd even need to write a dialog method. We're all in a rush to get things working, sometimes we can forget to clean things up at the end, and ARC will just automate that for you.
Putting this all together, ARC follows the convention so you don't have to. You can stop worrying about the procedural aspect of retain and release, you know, where do I put the retain, where do I put the release? You can actually focus on the object graph. And you can most of all focus on making great apps.
So let's talk about migrating to ARC. What is this process? How does it work? Well, we're going to talk it through. We're going to talk about the migration steps, the common issues you might run into and how to fix them. We're going to talk about the deployment of ARC and where you can use it. And then finally, we'll do a demo of migrating to ARC.
So, first of all, you need to be using the latest compiler. ARC works as of 3.0, but 4.0 was announced at this conference and has all the latest, greatest optimizations, and you should use it. You use CONVERT to Objective-C ARC under the Edit menu and refactoring of Xcode.
And you just work in an iterative process. And as you fix issues, you just go back to Edit, say Refactor, try the CONVERT. And once all the issues are fixed, you'll be presented with a dialog box asking for your approval for the changes. There it is under the menu. It started with Xcode 4.2, but it's available even better with each release.
Once you are done fixing the issues, the diff you get presented with, you'll see all the calls for retain and release are deleted. As we talked about earlier, one of the rules of ARC is that you can't call retain and release or auto-release. During the migration, we have to delete those from your code. Similarly, for simple uses of auto-release pool, we will just convert those to at auto-release pool for you.
And finally, since assign is a dangling, unsafe, unretained semantic, we will convert those to weak and give you safe behavior for your weak properties. So there's two phases. Again, we're going to migrate and fix problems in the iterative process, thus we're going each time to the Edit menu and trying again as we fix issues. And then the code will be converted and present you a diff.
So one of the first issues we'll run into that was a warning before ARC and now is an error is a missing method declaration. Because ARC needs to reason about the memory semantics of the code you're writing, it needs to actually have the prototype, the declaration, so that it knows what's going on. So, for example, if we had a missing declaration here, the compiler will emit a warning saying, "Hey, the receiver, we don't know what this selector is. You know, hey, tell us what's going on." Well, it's real simple. We'll go back to our interface and fix it.
Similarly, let's say you were trying to be hyper-optimized and you wrote an auto-release pool like this so that periodically through the event loop of this while loop, we would drain the auto-release pool and create a new one. Well, the great news is that under ARC, we actually optimize auto-release pools, and they're significantly faster. So you shouldn't even need to do this. So you can just delete this code to fix this.
And of course, this is the warning you'll get that it's unavailable this entire class and you need to fix it. Here's an example. Because it's so fast, we can just drop it right into the while loop and not worry about it and just let ARC do the right thing.
In fact, it's six times faster than Snow Leopard, and it's awesome. So here's a subtle one. Let's say you have a switch statement and you love the fact that you can declare variables anywhere you need them. So you have a right inside of your case statement, you declare a new variable named date and you assign to it. Well, you're going to get an error from the compiler that the switch statement is in a protected scope. And because switch statements are basically syntactic sugar around go to, the compiler has a difficult time modeling what's going on.
Well, there's a real simple fix. Just add two curlies, give a bounded lifetime to that variable within that case statement, and let the compiler know your intent. Real simple. Similarly, the Singleton pattern can get really interesting. Let's say you were implementing some kind of activity indicator and you only have one of them in your process. You might have said, "Well, you know what? I want this to be really awesome. I'm going to take retain, make it a no-op, release, make it a no-op." All these things are basically no-ops.
Well, There's a bunch of ways we can fix this, but first you're going to get a bunch of errors from the compiler. As we talked about earlier, in order for ARC to be able to reason about your code, it needs to know what these methods do exactly. And if you're implementing them, then ARC can't know because it's just unbounded. So we can't let you implement those.
So one way you could do this is you could do a simple singleton pattern where your alloc with zone method allocates a shared instance. And once that shared instance is allocated, it always returns that variable. Now, of course, your init method is going to have to guard against this and make sure that we don't reinitialize the shared instance. So that's kind of complicated.
Now, of course, you could change this so you actually had a plus method, a class method, and allocate your shared instance that way. Now you don't need to worry about having init protect against it. You don't need to override this alloc with zone thing. You can just make it a much more formal part of your design that there's a shared instance.
Similarly, if you're worried about concurrency in your code, you could wrap this up with a dispatch once and use GCD to make sure that there are no race conditions in this shared instance indicator, shared indicator. One of the things we'd like to point out, though, the classes themselves are instances. So you could actually just refactor your code into a static global variable and then change those minus signs to plus signs for your singleton pattern. And then you can just say, hey, square bracket activity indicator, show.
And ta-da, you have a singleton pattern, taking advantage of the fact that classes themselves are singletons. Similarly, the delegate pattern gets a lot better. You can use week now. So in case you forget to say set delegate nil as a part of your cleanup, week will just do the right thing and set it to nil for you.
So let's talk about deployment. All of these things we've talked about are available as of line in iOS 5. As you saw earlier in the conference, the adoption rates of both of these operating systems is just very impressive, with 80% of our iOS users already on iOS 5. And I don't remember the exact number, so I won't make it up for Microsoft 10. But that was just really ramping up.
So Lion in memory management. As you've possibly seen in other talks, ARC has been really successful. We have no heap scans, no whole app pauses, no non-deterministic releases. Well, what does this sound like? In fact, ARC matches our framework conventions really well. We've really grown to design our frameworks to rely on the exact order of the release of objects, and ARC automates this very well. Because of all this, we're deprecating GC. We really think that a smooth, fluid user interface is very important to our customers, and great battery life is just as important. So with that, GC is deprecated.
With that, I'd now like to switch to the demo and show you what this migration process looks like. Right here, I have an app called WikiHow, which I'll just click run to show you before ARC. So it gives you basic advice on how to solve problems that you might run into.
So you can click around, obviously. There we go. So let's start the conversion process. We go to Edit, we say Refactor, Convert to Objective-C ARC. Now, we can do this on a per-target basis if we're not ready to switch all of our project. And if you're really advanced, you can go through and do it on a per-file basis. But for the conversion process, it works on a per-target basis. Thank you.
So let's give it a whirl and see what problems we run into. We ran into 12 problems. Okay. So let's start with an easy one. All right. Well, we can't do these. We can't implement, retain, release, all these things. So let's just delete that and let ARC do that for us. Great.
So here's a basic cast. Well, we know that this is not transferring any ownership, so we'll just do a bridge. And oh, here's one of those tables we talked about where we have a global table of ASCII escape maps and we have an NSString in. So here are the NSStrings that we see that are just global. Oh, great. Let's just say unsafe, unretained, done. No visible interface. So what we're missing here is the -- sorry, multi-line. The init with specifier is missing from our interface declaration. So we go to the -- let's go to the implementation.
Classes.
[Transcript missing]
So here's another case. Now here's a case where the compiler is issuing a fixit. Just tells us, great. I'll do another fix-it. These are, again, returning plus zero. So here's another case where we're doing a context parameter where we need to do a fix it.
[Transcript missing]
Great. We've fixed all the issues and now ARC is about to present us with a diff. It's generating the preview, it's removing all the retains and releases and auto releases, simplifying our dialog methods, converting our auto release pools over to the new simpler and faster syntax.
All right, great. So we get a big diff. As you can see here, a bunch of code is being deleted. So we got an entire dialog method deleted here. Similarly, there's that and there's... All these releases are disappearing. Autorelease disappeared. All of the right thing happens and the code gets simpler. So with that, we can click Save.
we can click run. And then ta-da. So there we go. Now we're back under ARC. We can click around. How to green your business. So ARC is really efficient. Great way to green your app. So with that, I'll switch back to the slides. So as you've possibly seen this slide earlier, we've been simplifying Objective-C from the very beginning.
We've added a thin but very powerful object-oriented layer on top of C. We added retain-release to make it easier for developers to coordinate between objects. We added properties to simplify your getter-setter patterns and synthesis to simplify the implementation of those getters and setters. We added blocks to make enumeration and callbacks very easy. And now we've added ARC to delete even more boilerplate from your code.
So for more information, I'd invite you to contact Michael Jurwitz. We also have a very extensive programming with ARC release notes that covers all these things in great deal as well. And then there's always the developer forums we like to hang out as well. We have some related sessions.
There was a session earlier that you'll have to catch on video now, but it's Modern Objective-C. Tomorrow we have a "What's New in LLVM?" talk. And we also have a "Migrating to Modern Objective-C" talk tomorrow. If you have a friend that missed this talk, there's a rehash on Friday. And with that, thanks for coming.