Leopard Innovations • 58:09
Gain advanced insight into using properties in threaded applications, implementing fast enumeration in your own classes, and designing rock-solid applications with garbage collection. This session builds on the concepts introduced in Coding Smarter with Objective-C 2.0.
Speakers: Patrick Beard, Greg Parker
Unlisted on Apple Developer site
Transcript
This transcript has potential transcription errors. We are working on an improved version.
I'm going to talk to you about Objective-C 2.0. Objective-C 2.0 is very heavy on programmer productivity. We have a new feature called Fast Enumeration, which gives you much more efficient, better access to -- to the elements of your collections with properties, which give you a convenient way to declare, to access, use accessor methods and implement them. Saves you a lot of time.
And garbage collection -- we have garbage collection, which makes it a lot easier to manage the life times of your objects. So the first part of this talk I am going to be talking about Objective-C 2.0 language sfeatures, the second half of the talk about be about a new runtime. And then go to Session 165 where Blaine Garst is going to be talking about garbage collection in detail. So let's dive right into Fast Enumeration.
Fast Enumeration gives you a much cleaner, faster, easier to understand way to access the elements of your collections. In the past you would use an NS enumerator, and if you use this new loop that we call informally the for...in loop. There are two forms of this loop. One allows you to introduce a variable that's bound to the scope of the loop.
The other let's you use a predeclared variable which is great for searching the contents of a collection, and you can break out of the loop when you find what you're looking for. And if you fall through and don't find what you want, that variable will be nil. So you can tell, foundation collections will never have nil in them.
So why would you switch to this kind of a loop when NS enumerator's been around a long time; it's served us well. One of the reason is it's wicked fast. It's so much faster to use with arrays because in essence you're able to bypass encapsulation in a very safe way, and get direct access to the pointers where the array elements are stored.
This -- this graph here shows that for arrays, Fast Enumeration is the fastest way to go. The blue line is your typical for...loop with object at index. The green line is the NS enumerator, and the vertical axis is basically average time per iteration of a very simple loop. So it comes in the fastest. So why wouldn't you use it.
It's also safer. Fast Enumeration will detect if you happen to have some code that's side effects the contents of a mutable collection. Part of the Fast Enumeration protocol is code -- is part of the loop. Is code that's compiled in that checks every time we've done an iteration of the loop. Has the collection changed. So that turns mysterious crashes into exceptions that you can catch and identify. It's also very flexible. You can teach your own classes how to do Fast Enumeration. And I'm going to show you later an example of doing that.
So how does it work. Here's a basic loop. Summing an NS array of numbers. It's just a function that returns such an array and sums all the NS numbers up. So what does the compiler do for you. It looks pretty simple. Well, thank God you don't have to write this code.
All the orange is inserted by the compiler, and you can see the original code mixed in with it. So what the compiler does it creates a special instruction on the stack that has the -- keeps track of the enumeration state of the loop. It compiles in, checks to make sure the object that you're trying to enumerate over implements the Fast Enumeration protocol.
And I didn't show it, but it also has code for each iteration of the loop to check the mutation. It's basically a counter or a word in memory that's cached. So what it does is it calls this method count by enumerating state. And it gets the elements out of the array in as few chunks as possible. Sometimes just one call to access all the elements. Then it fore-loops over those, and accesses them directly by pointer. So it's really fast; it's great stuff.
So here's an example of a class that you might write that implements Fast Enumeration. This is a file reader hypothetical class. I've written code, tested that this works. You allocate one, you read a text file, and then you treat it as if we have a collection of strings.
And you could just use for...in on it. So what's makes it work? This single method. This is enough. All you need to make the for...in loop work. So when the loop is first called we detect first call by checking the state, field of the structure. Do some required initialization.
Then we actually read the next line out of the file and if we get nil back we know we're done. So the return value of this method is the number of elements available on the current iteration. So this is real simple. You could actually enhance this a little bit by having it read ahead a few lines and return a check of it.
But probably wouldn't matter in this particular case. So that's it. The Fast Enumeration protocol is really simple. It's fully documented in the Objective-C 2.0 programming language document that you'll find in your documentation. And it's a lot of the repeats of the word document right there. We go onto the next topic, which is protocols.
So protocols are mostly used today in Cocoa to model very simple, usually a couple methods -- simple contracts between one part -- one class and another. But there are many cases in Cocoa where we need to be able to say yes, we need a set of methods, but we only want a few of them to be required. So we've added that feature to protocols from Objective-C 2.0.
Call them optional methods. So a great example of this is if you're writing an NS table, UI, you need to implement an NS table data source, which is, today, what we call an informal category. And we use informal category -- I'm sorry, informal protocols. And we use those, because those allow you to specify a bag of methods and say, well, these are required and these are optional. But there's no compiler enforcement of that. We just see comments or documentation. So now we can recast the same thing as a protocol, and we introduce two new keywords: At Required and At Optional. At Optional implemented; no problem.
So these new key words, At Required is default. So if you haven't specified it, you have a backward compatible protocol. All the methods in that protocol are required. And again, if we start using these more for delegate type things, then the compiler can tell us if we actually implemented it correctly. So protocols, you will probably start to see more and more protocols with optional -- I know there are at least two or three classes in the AppKit today that are brand new delegate types things that are using them.
One other thing to say about -- and here's the syntax, just for completeness. One other thing to say about protocols is they are now completely opaque to the runtime. The fact that they were objects in the past -- well, just forget that. We provide a set of cover APIs.
32-bit mode, you can still call the methods on the protocol method, and 64-bit you must use Protocol Get Name, for example, to get the name out of a protocol object. So it's just becoming -- it's a general trend in our 64-bit runtime. Anything we can hide, we'll hide. More details on that later.
Class extensions are a new feature. They are, again, to substitute in for what categories have been used for what historically. One of the important uses of categories is to create a private API to a class. Here's an example of a class that has a public getter, but only -- but a private setter. And it's expressed as a category. But again, the compiler enforces this. If you've declared it, but you forget to implement it, the compiler won't complain. You'll find out at runtime that the thing you're trying to use is not there. So it's simple syntactic transformation.
There it goes. Relieve the name. And now you have what we call a class extension. This changes the behavior of the compiler so that this piece of interface is required to be implemented in the add implementation. So you can have as many of these as you like. So you can divide up your class into multiple pieces.
So if you forget to implement the set name method in this example, the compiler will warn and say, hey, what are you doing? So it treats that declaration as if we were lexically part of the original class. So there's the syntax. You can put in methods or property declarations.
Which leads me to properties. So properties I think are our biggest future in the direction of improved programmer productivity. They provide a way for you to declare, to use, and to implement accessor methods. That's what they are. They're basically all about accessor methods. And one of the greatest innovations of properties, we discussed this last year, automatically generated accessor methods.
But everything I told you last year; forget it. We changed nearly everything about Properties. The only thing that's the same is, well, yeah, you can dot them. One of the perhaps most interesting changes we've made is that anything that looks like an accessor method you can use the dot operator on.
So even if we haven't gotten around to creating property declarations for common accessor methods, you can use them as if we did. So more details on that later. We have some new keywords for synthesizing accessor methods. We've redesigned attributes, we've made them more regular; easier to understand. You'll tell me, of course, if that's true. And synthesized instance variables now are only part of the 64-bit runtime.
So how do you use a property? Well, you can walk up to an object and if it has an accessor method you can use the dot operator to access it. So, okay, it's one character fewer to type. Well, those characters add up if you have deeply nested expressions. So that's pretty nice.
This feature is as I said independent of the rest of the talk. So if you only learn this, you can start using the dot operator to start accessing your properties. It works with any name, set name pattern by default. That's the stock behavior. But you can customize that. The only constraint is that X has to be a known type. It can't be an I.D.. And if it is a class type, it can't just be forward declared.
So we declare properties with a simple syntax. We use the At Property keyword, an optional list of attributes. If there are no attributes specified you can actually declare them without any attributes. You don't need the parenthesis. Type and a list of names. And that's equivalent to declaring by default, the pair, the name and set name pair. You can use them anywhere you would use methods today. Classes protocols, categories and class extensions.
And I'm going talk to you more about these attributes. Because they've provide sort of this enhanced capability. The ability to have your properties described to both your clients and to the compiler how they should work. How they should implement. They go a long way toward supporting existing practice in Cocoa.
So there are four broad categories of attributes. One is for controlling mutability. You can say, yeah, this property is read write, or read only. There is ownership to talk about, when object values are stored in properties. How they are managed. And there's amnesty, which has to do be correctness under multithreaded programming. Finally, there's a way to teach the compiler the naming convention you want to use for your accessor methods.
So mutability is all about read only or read write. We don't have read -- write only. Somebody asked about that the other day. Classes -- sub classes -- base classes are allowed to say this is property is read only. But a sub class can come along and change that. And this models the typical immutable base class, immutable sub class that you find all over foundation.
So here's an example, the old way, the current way NS data says, yeah, I'm immutable. There is nothing you can do to touch me. You can't touch the length of my data. But mutable data comes along and says, sure you can. You can change the length. With properties, we would change it to this; which to my mind is a little clearer, because you're saying, yeah, the base class can only read -- the sub class can read and write.
Another thing you can do with this though doesn't require inheritance at all. You can use it in a class extension. So if you would like to have a class which is publicly read only, as a publicly read only property, you can turn around and make it read write in a class extension. Which then allows you change it internally, or some other class you trust.
The next set of attributes have to do with ownership. The object ownership. The very first one, assign, is the default. And it models two things. This is where we first want to talk about garbage collection versus retained code, or retained counted code. When you use the Assign Property attribute -- property attribute -- by default that means you simply get an assignment in reference counting code.
It's an unretained pointer. Which means it could be a dangling pointer under reference counting code. Under gc, though, a simple assignment is sufficient to create an owning reference. So that there is a difference in the semantics. If you're going to write gc-only code, this would probably be what you want to use most of the time.
The next attribute is what you want to use if you need code that is compatible across garbage-collected or retained counted code. If you're going to create a framework that needs to run in both. Retain means use retain to manage the lifetime as a side effect of assignment, retain the object, release the old. And your getters will call retain auto release to give you a little bit more safety with respect to auto release pools.
Retain also, though, has the same meaning as Assign under garbage collection code -- collected code. Copy is just the same as retain, but it says as a side effect also use the NS copying protocol, copy the object. Now these are contracts; these are specifications. If someone reads your property declarations they'll know that's what you're doing. It's a standard behavior. But if you write the accessor methods it's your job to adhere to these contracts. Which brings up the whole rationale for wanting to use synthesized accessors, because you don't have to do the work then.
Now for multithreaded programming, we may decide to make it a possibly that all accessors should be atomic. So if you use any of our built in accessor methods with any of these previously mentioned attributes you're guaranteed to get atomic updates. For the simple scaler values, you know, the compiler machine arranges that for you.
But for retain and copy and structure that's you might have written into yours, you have to take pains to do that. The whole cycle of retaining and releases the old and retain auto releasing means that the code has to run in a critical section. So if you use our -- as I said, if you use our synthesized accessors, you get that for free.
But there are going to be some times when you need to escape from that. Because you're paying that cost every time you access a property. And if you're going have something that requires sort of a complicit locking anyway, because you're, let's say, in the update to property values, and then that has to happen automatically. Then you can actually optimize your accessors and advertise that you do so to a potential client. Hey, you need to lock me before you use this property. You can do that with a nonatomic attribute. We don't have an atomic attribute that's implicit.
Finally, the final set of attributes are called the accessor naming attributes. And they are used to be able to cause your properties to be able to adhere to a pre-existing naming convention that you might have. I show here one where you might have a class that has an invisible -- visibility control.
And it has its accessor, a typical boolean accessor using the prefix is -- so is visible. So I just made one up here. Imagine you have a setter called make visible. Well, you can -- you can model that. You can do that with properties so that if you simply say object dot visible, you're either reading or writing calling these accessors.
This is different, again, from what we presented last year, which this was really an aliasing mechanism. Now it is a straight directive to the compiler. Use these selectors when you use your properties. The main constraint is that these methods have to obey the correct types. The correct return type, the correct argument type.
So here's the summary of tables showing them in the grouping. And this leads me to the next part of the talk where I want to now tell you about implementing properties. So as I have implied up to now you can write your accessors by hand. If you have a whole bunch of code that you have already written, you can actually use convert to Objective-C 2.0 in Xcode. There's a refactoring tool that will take all of your existing property accessor getter setter pairs and rewrite them as properties, if you like.
But if you're going to write new stuff, might give the compiler a shot at it. And then there's some extra runtime magic that I want to tell you about that might get you -- help you get yourself out of a corner you painted yourself into. So to synthesize an accessor we have provided the At Synthesize keyword.
And this is included in the implementation section of your class. Rather than specifying something in the interface, which we did last year, we decided this is information that's not terrible interesting to the clients of your -- of your classes. It's sort of an implementation detail. So this appears in inside the add implementation block.
And the -- as I mentioned earlier, all the other attributes are used to guide the compiler when it implements these things. So if you specify copy, it's going to call copy on the value. If you specify retain, it's going to call retain. So here's an example of using Add Synthesize with a car object, two instance variables, position and velocity. And we want to have two properties. One named position and one named speed. So this shows off what you can do. Just like getter equals and setter equals, you can change the accessor. Here you can change the ivar (assumed spelling) that the synthesize accessor uses.
So here's a complete example for 32-bit code. This creates a fully functioning class that will respond to these messages, and you're done. Well, there's things to think about like memory management and inits and deallocs (Phonetic). But -- the 64-bit, though, you're even -- done earlier. Because you can remove those two instance variables. So it's -- an added convenience that in 64-bit code the compiler will actually add the instance variables to your class for you.
The thing I didn't mention is when At Synthesize sees a name, it assumes there's an instance variable that matches that name. That's why we have this -- this extra syntax, to be able to remap the instance variable. But it's implicit in 64-bit mode. If the ivar isn't there, it will add it. That works because in 64-bit mode we have non fragile instance variables.
The next thing that we can do -- this is what I alluded to earlier -- is a little bit of runtime magic. We have this other keyword that says, you know that property that I just declared. Well, forget that declared it. I am not going to implement it. And don't bother me about it. I know what I'm doing. So this class shows a property -- a boolean property -- and it tells the compiler, this probably will just come, be implemented, as if by magic.
So an example of using this is Core Data. Core Data today in the seed you have allows you to declare sub classes of NS Managed Object, and today or in the past you would declare old school accessor methods. And you would write boiler plate code which basically goes three methods and the only thing you ever change is the name of the key. So it's not doing very much for you. Wouldn't it be nice if you don't have to write that code. The next way, the better way would be to use a property and to say, hey, it's dynamic. Don't bother me.
Core Data already knows how to turn properties that are specified just by name into accessors into the NS Managed Objects object store. So wouldn't it be a great idea if it could do that. If it could provide the accessor methods for you. Well, it does. It does that through a mechanism that we call dynamic method resolution.
And this is a new runtime feature that is invoked very early on. If the runtime discovers that you're trying to call a method that isn't defined in your class it will consult your classes plus method, resolve instance method, or resolve class method, and ask it, hey, do you got anything for me? If it does, if you do, you add it to the class. You can do lazy binding this way. And return yes. If you don't, you defer to your superclass and hopefully it knows something you don't.
So this will happen as I said on the first call, where a method hasn't been -- is discovered not to be defined. If you don't provide something then the old way of handling it with occur. Either you'll get an exception raise because the method doesn't exist, or programs you will have a forwarding call go -- happen -- that does something else. Like a proxy. This can be leverage for lots of interesting things. Bridging to other programming languages. Perhaps some lazy initialization of your class. And it's fully documented, again, in the Objective-C 2.0 programming guide. And that's it. Greg Parker's going to take over now, and thanks a lot.
( Applause )
Thank you, Patrick. I am Greg Parker. I am the runtime wrangler, which means I write code in the low-level Objective-C 2.0 runtime, which is the underpinnings for the entire Objective-C 2.0 and Cocoa language system. I could talk about some new features we have in the runtime. These are all new in Leopard.
Some of the features we've added are some syntax for method class attributes. I'll show you some examples. We have an enhanced low-level C API, for those of you doing low-level runtime manipulations. We have several new Objective-C features in 64-bit only. I'm going talk about the 64-bit API, which is the representation of your program's meta data.
And then three new features. Instance variable and class access role for those of you writing frameworks. Non fragile instance variables, which are also interesting for those of you writing frameworks and others. And finally, 64-bit zero cost C++ compatible exceptions. For those of you who know what that means, you'll really like it. This is multi for C++ developers.
So let's jump into the method in class attributes to start with. So attributes are a GCC syntax for specifying additional things about a method, about a function. We've added the ability to add them to Objective-C 2.0 construct, including classes, methods, and properties. So some examples of what they've useful for, you can add an attribute for deprecating a method, or specifying which OS versions a method works in. You'll see this all over the foundation and AppKit.
You can add a -- an attribute that specifies whether the parameter list method must have a nil terminator. So the big example of this is the array with objects method, where you specify a list of objects and then a nil. If you leave out the nil the computer crashes. If you add the attribute the compiler will warn you first.
Finally, we have a parameter -- an attribute which is actually not quite implemented yet. For specified parameters in a method that are unused. So let me show you the examples of these. The first one we have are availability and deprecation attributes. The first one here is a class that is deprecated in Mac OS X, 10.5.
So if you try to use this class the compiler will warn you that you're using a deprecated class. We have the same deprecation warning for the deprecated method, and a similar macro for a property which has been introduced in 10.5. If you set your deployment target to 10.4, you get a compiler warning.
Next example is the nil termination attribute. There are other attributes that you can use for different sorts of termination and method lists. But this is the one you will actually see most often. So this is a foundation macro. The array with objects method takes a variable number of arguments, and the last one has to be a nil. And that's what the -- requires nil termination -- macro tells the compiler to warn you if you left off the nil.
Finally, the unused parameter attribute. This one only works in GCC 4.2, which is, you know, not quite out yet. But you will see this in the future. It will be available to you. The example here is an IB action, which very commonly the sender parameter you see isn't actually used anywhere in the method. If you turn on GCC warnings it will warn you, you have this parameter; you didn't use it. The unused attribute tells the compiler I have this parameter, it's unused, and I know it's unused, so you don't have to warn about it.
Next feature I am going to talk about is the low-level API changes. Probably, only 10 percent of your or fewer care about this. But those of you who do, it will be pretty important in Leopard. API additions. We've added new features to the C level API, to the runtime. The primary edition is function-based replacements for all the meta data structures. This includes the struct OBJC class, struct OBJC method, et cetera. If you wanted to read those structures we have functions to access it. If you want to modify those structures we have functions to manipulate them.
As Patrick mentions, protocols are not really objects any more. And particularly in the 64-bit runtime they do not respond to any messages. So to replace the name method, description for instance method, we've added some C level API to manipulate protocols due to access protocols. One additional advantage of the C level API is it provides features that we didn't have before. The ability is to look up a protocol by name. You can say give me a protocol for the NS copying protocol at runtime, instead of using the app protocol syntax, which is appropriate for some code.
Another addition we have for those of you who are constructing classes at runtime, we have a new set of functions to make that much easier; much simpler. And much less likely to crash if you get something slightly wrong. For creating classes, adding methods to those classes, adding instance variables to those classes at runtime.
Finally, for those of you who actually look at all this stuff, all the header files have been rearranged in user include. So be aware of that. It's pretty simple. Pretty straight forward. API subtractions. Not everything is free. There are some costs. We have removed some things or deprecated some things that the runtime API. The first thing I mentioned about these changes are they're deprecated in 32-bit and they're simply gone in 64-bit.
If they aren't gone in your seed they will be gone in October. So be sure to fix those deprecation warnings in 64-bit mode. So what are the subtractions. The primary one going back to the first slide, all the meta data structures are deprecated. OBJC Class; gone. OBJC Method. Gone. Well, gone in 64-bit. They're still there in 32-bit if you want to use them.
Use the function-based replacements to manipulate the things you would have manipulated anyway. In general, there are things -- there are functions to manipulate anything that you would have changed previously. If there's not a function to change it, it probably means that you can't change it in the new runtime anyway. We don't want you to do that.
One particular note about these changes, if you have any variables of type structure, object class. That type is deprecated, you should use the class type instead, which is not deprecated. Doesn't have any variables in it, but that's okay. For non deprecated use the class type is what you want.
As Patrick mentioned all the methods on class protocol are deprecated. Use the function-based replacements for them if you are manipulating protocols that way. For most of you the only thing you're doing is a line like this. Class conforms to protocol at protocol. This still works, nothing deprecated. Nothing evil about this. It will all work fine. If you're doing something for sophisticated with protocols you will probably need to change your code.
Finally, there are some other random functions in the old API that have been deprecated or removed in the new one. A couple of examples here for manipulating method lists. The big example for a lot of you is class posing. Class posing is now completely absent in the 64-bit runtime. You cannot pose as another class.
For most things you want to do with class posing, you can use method list manipulations to do it. You can add a method to a class, you could change the method implementation of a class's method. The code might be a little bit uglier. But face it, you're pretty much going behind the scenes hacking anyway. So we consider it acceptable that you have to do a bit more work to do your hacking.
Next feature I'm going to talk about is a 64-bit runtime ABI. The ABI is the Application Binary Interface. Which is how the meta data is structured. What does a class structure look like. What does a message sent look like when the compiler writes it. 64-bit, we have a brand new ABI taking advantage of the fact that we're breaking capability anyway, because there isn't any 64-bit code to be compatible with. A new meta data format on disc.
The big design feature here is some performance enhancements. But also much better future expandability. When we want to change the ABI, we will have the flexibility to do that without breaking any existing programs. For those of you looking very carefully at the ABI data you'll notice first of all that there is no OBJC segment any more. All the meta data is in a new format. It's in different places now. Results and new meta data format in memory. In particular, the class structure is different. The method structure is different. Which is pretty much why we've deprecated all those structures for you.
And that's actually all I'm going say about the 64-bit ABI. d All the rest of the details are private. They are subject to change. They will change in the future. Thus, the future expandability part I noted about. For those of you who are writing any sort of tools, introspection tools, compiler tools, that sort of thing, we will be providing documentation about the API.
But it's only going to be valid for Leopard, perhaps. We may have changed it by the time we get around to whatever the next major OS is. One note about -- final not about it is the Objective-C runtime is open source. But open source does not mean documented.
Open source does not mean forward compatible. Feel free to learn whatever you like by reading the source, by looking at the data structures the open source compiler and open source runtime use. But don't write code that depends on it if you want that code to run in future OS versions.
Finally, I am going to talk about three 64-bit only features that we've added to the runtime of the compiler. These features are all taking advantage of the fact that we can change the binary compatibility layout, we can add new features to the language. At this point in 64-bit where we couldn't add them in 32-bit. First of all, I am going to talk about instance variable and class action control. This is going be primarily interesting for those of you writing frameworks that are providing classes or providing instance variables to clients.
So one feature of the 64-bit runtime is private instance variables, the hidden classes. First of all hidden classes are new. I'll tell you about them in a minute. But private instance variables and hidden classes are more strongly enforced if you're trying to access them outside from the ordinary protection domain where they're allowed to the accessed in. What does strongly enforced mean? Strongly enforced means if you're using somebody else's private ivar your program may fail to link. Because you're using that private ivar.
For those of you writing applications, don't access private ivars. For those of you writing frameworks, you can add private ivars to your classes, mark them private, and be more assured that your clients won't be accessing them behind your back Let's talk about instance variable visibility. Again, for those of you writing frameworks with instance variables in them.
At public and at protected instance variables behave the same as they always did. You may use them anywhere. The compiler will warn you if you're using at private outside of a sub -- sorry -- Compiler will warn you if you're using at protected ivars outside of a sub class. But there's no other enforcement. There won't be a link area or anything like that.
At private ivars are where the enforcement comes in. And may only be used in the defining class. That's the definition of private. The compiler warns you if you're using it outside. The new feature is this. If you use the ivar outside the image defined in the class you get a link error. So by image, I mean the framework that implements the class. Or the bundle that implements the class, or the application.
For those of you who have -- who need for flexibility for your instance variables we've added a new ivar class called At Package. The way to think of that package is internal to your framework or your application. It's a public instance variable. But if anybody tries to use it outside of your framework, say, your clients try and access it, it looks private to them.
So this works, for instance, for instance variables you want to access freely inside your implementation for speed reasons, but you don't want your clients to access it. So just like private ivars, if you use it outside the framework or the library defined in the class you get a link error.
At Package works in 32-bit. It behaves the same At Public, because we don't have any stronger enforcement app framework at the framework boundary level. Class visibility. I mentioned hidden classes a minute ago. All classes in Objective-C are public by default. This is the same as it is in 32-bit.
In 6-bit we have a new feature where you can mark a class hidden if it is a class that you're only going to be using inside of your framework. So this is a class that is an implementation class or a private sub class, or something like that. This is the syntax for marking such a class, it uses one of the GCC attributes that I mentioned earlier. Oops. Sorry about that.
One problem with this feature is we don't have any clean syntax for it yet. Sorry about that. You have to use the raw GCC attribute syntax. One note about that; first of all the attribute needs to go before your At Interface declaration. Second of all, you must have as precisely as many parentheses and double quotes as you see here.
If you have any different number the compiler will complain because it won't recognize it. So what does a hidden class do? First of all, hidden class gives you a link error if somebody else tries to use the class outside of the framework. You try and access it directly, if they try and sub class it, they get a link error.
Additionally, all the ivars will similarly get a linker if anybody tries to access them out of your implementation. One down side of this is the hidden class is still in the global Objective-C class name space. Which means the class name still needs to be unique. So you still need the class name prefix at the start of the class. The hidden class do not provide that. We're looking at ways to provide a better way to do non unique class names in the future. But for now this is what we have.
So one point about hidden classes and private ivars. For those of you who really, really, really want to access them still. You can actually get to them at runtime. If you call OBJC get class with the name of a hidden class you'll get the class. If you call the instance variable look up foundation for a private I bar, you'll get the ivar. So you can still use them. But don't. You really shouldn't be doing that. There's usually reasons why they're marked private in the first place.
So manipulating class and ivar visibility. The best way to change visibility of an instance variable or a class is to use the language constructs. If you have ivars and they're internal to your code mark them private; mark them packaged as appropriate. If you have classes which are internal to your implementation mark them hidden so that anybody who tries to use them will get a link error if they try. There are other ways to restrict the visibility as well. Using the linker. So I won't talk about these too much. But if you use an exported symbol list.
Classes in 64-bit have a symbol attached to them. Instance variables in 64-bit have a symbol attached to them. That's why you get the link error if you're trying to use the wrong one, because those symbols are not exported. So you can manipulate your instance variable and class visibility using an exported symbol list or using the GCC option or Xcode option for hiding symbols by default. One point to note, you may already be hiding your symbols by default. But in 32-bit that doesn't hide your Objective-C classes or your instance variables.
So if you are using symbols hidden by default for F visibility hidden and you're writing a framework you may need to add this attribute to your classes which says visibility default. Which makes them public again, if you have classes that you did want to export, even though you said symbols hidden by default. So for more information on these details, on these options see the Xcode documentation, see the GCC documentation.
If you do try to access a class or an instance variable that you're not allowed to access you'll get one of these errors. Undefined symbol error from the linker. The first one is you tried to access a class; called some class. The OBJC Class, dull, or some class is the symbol name generated behind the scenes in 64-bit for that class. Similarly, if you tried to access an ivar from that class and it was a private ivar you would get that linker message for the illegal access to the ivar.
It's also possible that the instance variable is public. That the class is a public class. But you simply forget to link to the proper framework. So be aware of that when you see this sort of warning, this sort of error when you're building your code. Next 64-bit feature I'll talk about is non fragile instance variables. Non fragile instance variables basically make things work in the way that they didn't work in 32-bit. 2 So for most of you, you won't actually care that this is happening behind your back. Because like I said, it just works.
Let me talk about fragile instance variables. Which is what we have in 32-bit, what we fixed in 64-bit. This is what a fragile instance variable is. Classes in a framework cannot change instance variables without breaking sub classes in applications. What this means is NS View in 32-bit cannot ad2d any additional instance variables because all of your sub classes of NS View would stop working.
Fragile base class problem, for those of you who have studied language-based theory. This is what it is. Because we can't change NS View or any other Cocoa framework, it makes it harder to improve the framework. We can't add ivars when we want to once the class is released, once you guys have sub classed the class. In Objective-C we don't have this problem with methods, basically.
You can add a methods to a base class and the sub classes still work because method look up is so dynamic. There are still problems with if you've got method of the same name, if you took away a method someone was calling. That's a problem. But you guys can deal with that. You guys are smart. But in 32-bit Objective-C instance variables are decidedly not safe. You just can't add an instance variable to a class if anybody else has a sub class of it.
Let me show you an illustration of what actually happens if you try and add an instance variable and your instance variables are fragile. On the left we have NS window. Or a very tiny version of NS window with only a couple of instance variables in it. And on the right we have the pet store window, which is my neat pet store application which is sub classing NS window.
And I've added two instance variables for the list of kittens and the list of puppies that are available. So this is all well and good. The problem is when Leopard comes out. And NS window added a tool bar instance variable. The problem is the tool bar instance variable is at offset 24. And unfortunately that just killed your kittens.
Because all your compiled code expects that instance variable to be at offset 24. $ And it's not there any more. That's where the tool bar is. So this is a fragile instance variable. This is what happens. This why NS Window and NS View cannot add instance variables any more. What you really want to have happen, though, is this.
This is what happens in 64-bit mode. The 64-bit runtime and compiler, and the compiled code are smart enough to be able to move ivars around at run time. When the runtime loads the pet store window class and it sees that the NS window class is bigger than it was when the pet store window was compiled. It can move the instance variables. It can slide the kitten and the puppies out of the way of the marauding tool bar and keep them safe. And the generated code does not need to be recompiled to adapt to the New Instance variable layout.
This is what we've added in 64-bit. All instance variables in 64-bit are non fragile. Which is good. The compiled code uses the symbol I showed you earlier during the access control. That symbol points to the offset of that instance variable. When you access the instance variable it load the offset from that location. The runtime can then change that offset when it loads the class.
What does this mean for framework authors? It means framework can change their ivars without breaking their clients. The specific changes you can make. You can add ivars to a class and no sub classes will care. You can move ivars within a class, and the sub classes won't care. You can also delete and rename instance variables that you already have and sub classes won't care unless, of course, the sub classes are actually using that ivar. In which case they will get a link error when they try and run.
A couple of things in your code that you should not do to ivars because they are non fragile. Because the non fragileness sort of breaks these -- this sort of code. Particular things you shouldn't do. Don't use at depths. The at depths construct gives you a structure representation of your instance variables. And of course if the instance variables move at runtime that structure is wrong. So don't do it. Don't use size of a class.
It's possible that the sub class added instance -- or a superclass -- added instance variables which changes the size of the class itself. We have a runtime function you could use to get the size. For the same reason, don't use offset of and ivar. Instead, use the C functions to look up the I bar, look up the size at runtime.
Finally, non fragile ivars only applies to structure and bit fields as the entire structure, or the entire bit field. The individual fields inside structure or bits inside your bit fields are still fragile if you array arrange them inside the structure in the bit field. So if you have an instance variable which you expect other people to use and its the structure type, don't rearrange the contents of the structure in your framework or in your class.
Last feature I'm going to talk about, for those of you writing C++ or Objective-C++ Code, zero-cost C++ compatible exceptions. Now I heard earlier some of you guys thought you would like this. Let me show you why. This is a performance graph, showing you what zero cost means. I put it in scare quotes there, because it is not really zero cost everything. It's zero cost some things.
So this is showing performance of executing empty blocks, executing try finally blocks, and executing exception throwing The blue columns are 32-bit runtime. And the green columns are the 64-bit runtime wdith quote, unquote, "zero cost" exceptions. So the column on the left is just the baseline. Speed for executing lots of empty blocks. And we see actually Intel 64 is a little bit faster than Intel 32-bit. 2 Yay for the new instruction set! So the middle column shows you try finally blocks. And this is the part where zero cost exceptions are faster.
That big blue bar means that 32-bit exceptions simply using an at try block is slow. Because an at try block means execute a lot of code in order to prepare for an exception if it actually were thrown. 64-bit you'll see that it is within one millisecond. It's actually within the timing error of my test, of executing empty blocks to get to use the same number of try blocks. Try blocks are no more expensive than an ordinary block.
But there is a cost. So you don't get that cost for free. The problem is when you actually throw in an exception the speed is reversed. We see that the 32-bit is very2, very fast at throwing exceptions. But 64-bit is much slower at throwing exceptions. So the change is 32-bit does all the exception overhead at the try block. 64-bit does the same exception overhead when you actually try and throw in exception. So the assumption is you do a lot more try blocks, you don't do very many throws.
So 64-bit. Entering a try block is fast. Feel free to sprinkle try blocks throughout your code to help make your code more exception safe. On the down side, throwing an exception is slow. And one thing I didn't show in that graph, there's also a big memory overhead with an exception is thrown to pull in the exception unwinding information.
What does this mean? Throw exceptions in exceptional circumstances only. For the Cocoa frameworks themselves, they're -- the model is already fulfilled that way. We don't have a file not found exception, or you hit the end of your array exception. Instead we have things like invalid parameter for your programmer error. Or out of bounds exceptions. Again, a programmer error.
One note here that some of you might not be aware of if you are using exception handling code. AppKit assumes that exceptions being thrown are NS exception objects. So don't throw string objects, don't throw other objects. AppKit will get confused. So use that NS exceptions; use sub classes of it.
I mentioned one other thing earlier, which is C++ compatible exceptions. What does that mean? You guys are going to like this, for those of you who do -- 64-bit exceptions in Objective-C share the C++ exception machinery. Which means C++ exceptions, Objective-C code, and Objective-C++ code all work together, the exception models are the same.
( Applause )
Your source code doesn't have to change. The throw expression, the try expressions, the syntax is all the same. The only thing that's not source compatible is those of you who were jumping through big hoops to try to catch your Objective-C and rethrow C++. You might want to change that code. But otherwise, source compatible with 32-bit.
We've added one additional source construct to 32-bit and 64-bit, which is a default catch, like C++ catch dot, dot, dot expression. Which catches any exception. I'll talk about that in just a second. So what do I mean by work together? I mean all of this. C++ destructors are called when unwinding an Objective-C exception.
( Applause )
This didn't happen in 32-bit. In 32-bit your C++ destructors are ignored when unwinding an object to C exception. Which is bad. We fixed it in 64-bit. d Conversely, at finally blocks are executed when unwinding a C++ exception. Again, the exception models are the same.
The unwind mechanism is the same. The runtime is -- has no trouble recognizing finally blocks and destructors. In terms of catch blocks, C++ catch blocks will not catch Objective-C exceptions except for the default catch. So a catch dot dot dot will catch a detected C exception and you can rethrow it using a throw expression.
For Objective-C we've added the new default catch, mirror A C++. At Catch catches any Objective-C exception, and any C++ exception that's going by. You can use at Throw to rethrow them just as you would use a C++ throw to rethrow whatever you caught in a default catch. Two notes on Objective-C catch. The first one is the NS Handler Macro. If any of you are still using it, you probably don't want to use it in 64-bit any more. Because NS handler is defined as catch I.D.
Which means that it doesn't catch any C++ exceptions. So if you're excepting your NS handler to catch everything that goes by, it's actually going to miss any C++ exceptions. The other note is that the catch dot dot dot or the at catch dot dot dot, the Objective-C one, is available in 32-bit. 2 It does exactly the same thing as catch IDE would. So it doesn't add anything to 32-bit. 2 It's just provided for source compatibility.
And that's all I have for the new 64-bit runtime, new Objective-C features. We hope you like Objective-C 2. We've added some pretty nifty stuff in Objective-C, and some pretty nifty stuff to the runtime. For more information Deric Horn is the Apple Developer Connection guy to talk to. The framework evangelist. And we have sample code documentation and more documentation forthcoming in a couple cases for the new features available at the WWDC Web site.