Development Tools • 1:01:08
Objective-C has proven itself through the years thanks to the language's dynamic runtime, powerful features, and elegant syntax. Objective-C 2.0 takes advantage of what we've learned about language design over the past 20 years to leap further ahead in productivity. Learn about compelling new language features, including new keywords and language ""properties.
Speakers: Matt Formica, Blaine Garst, Patrick Beard, Greg Parker
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Good afternoon, everyone. My name is Matthew Formica. I'm the developer tools software evangelist in developer relations, and we've got some great technology to talk to you about this afternoon. So this is the Objective-C 2.0 Overview. And as you heard Ted Goldstein say in the Developer Tools State of the Union yesterday, Objective-C as a language is really Apple's secret weapon that helps us be far more productive than other companies with much greater numbers of engineers. So how does Objective-C let us be this productive? Well, it's due to a few reasons.
First of all, some of the great features that are in Objective-C. It's got a powerful dynamic runtime and introspection capabilities that allow us to write really flexible and reusable code. And Objective-C allows you to do that in your applications as well. Secondly, we've got great OS integration with the language.
We've got frameworks that know of and take advantage of the Objective-C language, the Cocoa frameworks, and they work really well together in a seamless manner. And finally, Objective-C as a language is really easy to learn. It's not the complicated spaghetti code you might get in some other languages. C++.
and others. Being in developer relations, I've seen many of you cross the hurdle to learn Objective-C over the past several years, and usually there's some hesitation at first, but then within a few days you've been up and running using the language. There may be a few of you here at the conference who are not yet using this amazing language.
I encourage you to learn it here at the conference. You can be fluent, you can be using it within a few days and really fluent within a few weeks. We've got this additional layer, Objective-C++, that allows you to actually intermingle Objective-C code with C++ code so that if you do have cross-platform C++ code that you need to continue you can continue to use that with Objective-C.
So Objective-C has these great benefits. They allow Apple and you as developers to be amazingly productive. Objective-C is a great language. But some of the original architects of the language at Apple got together and were thinking, "What could we do to make the language even better? What would it mean to improve on Objective-C?" And as they thought about this, they came up with some goals that an improved version 2 of Objective-C would need to meet.
The Objective-C goals would be fourfold. First of all, they wanted to build in first-class support for Cocoa idioms. Over the years, we've seen that there are common patterns to how you use Cocoa, and we wanted to make a fast path through the language to using those common patterns in Cocoa.
Secondly, we wanted to make the SQL perform even better than the original. And that's because the uses of Objective-C are far greater now and applications are far larger than they were when Objective-C was first invented. Thirdly, the science of language design has continued to advance, and there are other features that have been invented that we wanted to incorporate into the language. And we wanted to do all of this in a way that didn't break existing Cocoa Objective-C applications on the platform.
So what would it mean to make Objective-C achieve those goals? It would mean revving the language. And that's exactly what we've done. And so this session is about the improved Objective-C, Objective-C 2.0, that hits those design goals. And so what you're going to learn about in this session is all the new language features that we've added, including several that have not yet been talked about in the states of the union. We're going to tell you a little bit about some aspects of the rewritten runtime.
And actually, since in 64-bit computing, we aren't limited by the same ABI requirements that 32-bit applications have, we don't need to maintain binary compatibility. There are actually some features that we could only implement for 64-bit applications. So 64-bit apps get to go even further. And to explain these features and more, I'd like to introduce Blaine Garst, one of the architects of Objective-C2.
Let's see, is this mic working yet? Is this mic working? Okay, good. Let's talk about garbage collection first of all.
[Transcript missing]
It's an opt-in system. You get it by compiling with this magic little compiler flag. There's a checkbox in Xcode. I'll show you that in the talk following this.
All Apple frameworks work in both modes. So if there's a framework on the system, it will be GC-capable by lepidogia. We didn't quite get to all of them in the seed that you have in your hands, but all the major kits are converted, and we've run lots of code against them, including Xcode internally. And as you saw on a TED stage, we even went and talked to the Omni guys and got several of their apps up and running on it too.
So the basic scheme in order for our frameworks internally to run in both modes is that they still have to have all that retain-release code and logic in there. It's just that the runtime just ignores those messages. It's like, you know, in two instructions, we detect that it's one of these things and just bail out. And so there's no code, no retain-release code is ever executed.
The major new way to think about things, though, is that instead of a dialog method, you know, dialog methods sometimes really do something important, like let go of a resource. And so in those cases, you do need an equivalent. You need what we call a finalized method. And so if, and only if, you need to get rid of resources, you need to write a finalized method, and we provide support for that.
What's fun is we've added some more tool support to make that happen. You may have seen X-Ray showing garbage collections going on when a text edit was converted. It turns out that text edit needed no changes whatsoever to run in a GC mode. It didn't do anything really magic in its DLX methods, and so you just compile it with the GC flag and it comes up and runs. So very simple applications require very little work to get up and run. So as I said earlier, internally we've been using Xcode to kind of push our changes through everything else. We have some of the small apps and there's other apps internally.
We have, I think, a fairly small API for garbage collection. It is obviously about reducing your code, and so we don't have to force a lot of new ideas on you, but we do have a few things. We have a garbage collector object, which you can query about what's going on and to do a few critical operations.
We've updated map tables and hash tables from sort of opaque structures into first-class objects that deal with objects, including this new concept called a weak object, meaning there still are cases where you do want to have sort of a non-retained reference to something else. In Java, there's something called a weak reference, and when it's an object itself, and you can put a pointer into it. And when the referenced object is not retained, it's not a weak reference. And when the referenced object goes away, the weak reference gets zeroed. Well, we put that into the language.
So we actually have a weak attribute that you can put on your instance variables and on globals such that when the collector collects the referenced object, your instance variable gets zeroed out. It's very cool. We have had for some time now the finalize and drain methods. We started this work several years ago, and in fact, Tiger Reflex has a lot of almost GC-ready code in it. There's a lot of stuff. We have an allocate collectible routine to actually let you allocate collectible memory and do things with it.
And all this and more in depth is going to be told to you in this room in the session immediately following this one. It's too much to go into in this talk, and so that's just sort of a teaser for the next talk, really. So let's go on to the next topic. Fast enumeration.
We had, for fast enumeration, several girls. We wanted to make enumeration fast. I will show you some code examples later as to...
[Transcript missing]
Let's talk about enumerating arrays. Now, if you're a real object programmer and it's time to walk through an array object, you might start out by just getting the object enumerator and running through it. It's pretty nice.
But it has some expense. You have to allocate an enumerator object. And it turns out for safety reasons, over the 15 years since we put this in, it turns out that the enumerator objects actually had to make a copy of all the elements inside the collection in order to vend them to you safely. And making a copy of all those elements is actually expensive. So once the copy is established, though, in the middle of the loop, the inner part of the loop, it boils down to one method call per item. title.
[Transcript missing]
Wouldn't it be nice if we could simply write this instead? And so the cost with this, with language support, is two hidden method calls to go through the entire array. You get a benefit that we put a safety check in the middle. We check to see whether or not that collection has been changed while you're in the middle of the enumeration.
Because that happens, it turns out. Even though our documentation says that it shouldn't, you shouldn't trust on that, it turns out that there is a fair amount of code. And I could cite the radars, but we don't really want to get into that. So there's a hidden mutation safety check in the middle of that. And the other benefit, of course, is that it's concise. You don't have to repeat that NSString twice. You don't have to have an unsigned int counter. I always type int. I mean, you know, it just reads better. So I like that. It's a better way to program.
Let's show you a little bit of the performance going on here. So the first line is our new fast enumeration scheme. This is for arrays of size, you know, factor of 10 on up to 100,000. So it turns out that at size 10, you know, there might be a little bit of a setup cost still apparent, but by size 100, it's just linear with the number of objects. Remember, there's no object copying going on with this one. We're just going in, getting the raw pointer to the memory, and ripping through it.
The super-extreme code, you know, that big bunch of boilerplate, comes in pretty fast. You know, it's pretty competitive on a speed level. But remember, it is doing a copy of all the elements most of the time. Anything above 100 is a copy. So we've sort of done the impossible. We've squared the circle. We have gotten the speed without paying for the memory. It's a very nice win. We really like that.
Looking at the standard objected index shows you what some people have found if they've done some performance analysis. Objected index is actually pretty expensive. The fun thing here is that for large objects, that very first one, the enumerator example, is actually, you know, comes in ahead. But you know, arrays are not often 1,000 in size. So in the low sizes, you know, 0 to 100, the enumerator example is just way, way, way expensive.
So I talked about how we did a good job for arrays. This is a feature for all collections, however, and another common collection we have are dictionaries. And so this is the standard code that you have to write to go through the elements of a dictionary. And this is the code you're going to write to go through the elements of a dictionary now. Again, a little bit more concise. And again, remember that in all cases these objects were copied. Well, as of Leopard, those objects aren't going to be copied anymore.
So enumeration has gotten a little bit faster, and you're going to be -- if you compile and target Leopard, you may get some enumeration mutation warnings coming out of these guys if you've been cheating and changing your collections while enumerating them. Let's take a look at the graph of the performance for dictionaries.
Again, the first one is fast enumeration. We don't quite get to rip along as fast as we do with arrays. There's an extra null check. There's essentially one method call per element in this case. And the existing enumerator is, you know, we're a little bit faster than the existing enumerator, although in this case it was still copying memory. So again, we're faster without using the memory. And for small objects, for small dictionaries, the cost is just exorbitant. Clearly, you want to use the new language mechanism whenever you can to get through your small object dictionaries.
What you've seen so far syntactically is you put the type declarator right in the middle of the parenthesized expression, but you don't have to. If you already have an iterator variable that you want to use for other purposes, you can go ahead and use that also. Here's examples of both forms, showing that as a for statement, so the regular control flow operations work as you would expect. You can return from the middle, you can break out and have the last value assigned in there available for you.
Inside Cocoa, of course, the collections, mutable and immutable variations on the collections adopt this. The new map table and hash table objects also adopt this, and as I said before, all NS enumerators have an implementation of this. If you've coded your own, you can override this method and make it even faster if you know how.
These are just some of the objects. I didn't go through the header files and name them all, but it's there where you want it. So let me talk just a little bit about the implementation. It's fun. So when the compiler sees that language statement, it synthesizes this on-stack sort of state buffer with some pointers in the middle of it.
It calls into a batch enumerator to grab a bunch at a time. And once it gets a batch of them at a time, it loops through them doing a mutation check before it hands off to your code. The collections implement this method, and the method basically says, you know, you get this state field coming in at zero. That means it's the start of one. It resets it to be some internal state, sets a guard pointer for the mutation check, and sets the pointer to the real objects. And it returns how many of them there are. It's pretty simple.
You don't really want to have to remember this, but for reference, that's what the structure looks like, and that's the method that the collections implement. A simple implementation of that method for an array with just a list of objects is, as I said, you set the state to be 1 to say, you know, next time you're in, we're done. You set the mutation pointer, in this case, to yourself, because in this example, you're an immutable array. Set the element pointer and return the count.
So when the compiler sees this in your code, what it generates is this. It's a lot of code. You don't want to have to write that. You want the compiler to write it for you. That's what compilers are good for. So if you wanted to use that method by yourself, you've got to do this in your code. I don't know why you necessarily would.
And with that, let's shift gears a little bit. We've made a couple other changes to the language. One of them is to protocols. Protocols came into the language about 15 years ago and from one person's viewpoint here at Apple, they were missing something. Ali Ozer has been ringing our necks for years saying, "I can't use them to describe delegate methods." He calls those informal protocols and he's right. You can't use protocols. Protocols say, "Here's a set of methods you must define." They're a nice grouping function for that. So what we've done is has added a capability to protocols now. So what Cocoa does today to describe these informal protocols-- this is a real example.
The layout manager delegate has two methods. It's the first one I could find that only had two. These are either/or. Use one or both, you know. And he declares them as a category on NSObject just so that you have some type information available so you know what the types are to your arguments and stuff. Yeah, it works.
What would be better is if you had a protocol, if you could declare that concept in a protocol and say optional about the methods that follow it. And so we put that in as a-- Ali has not had time to actually rewrite all of Cocoa or all the headers yet, but he will because we did this already. Well, anyway, he will.
He has every intention of making this happen by Leopard GM. So in the future, what you're going to see in the header files is that in the layout manager, you're going to get to type the delegate being set, and then in your code, you get to say that you're going to be adopting one of those methods. And so that makes your code easier, the design of your code more easily expressed. This is going to be adopting this thing, and so when you set it, the compiler is going to be very happy with you when you do this.
Hmm. For completeness, there is also an @ required. And that lets you, you know, mix and match and design your protocol layout the way you want. And with that, I'd like to hand the podium over to Patrick Beard, who's going to talk about a feature you've seen several times already, and that's ""properties."" And that lets you, you know, mix and match and design your protocol layout the way you want. And with that, I'd like to hand the podium over to Patrick Beard, who's going to talk about a feature you've seen several times already, and that's ""properties."" Properties, what we really wanted to do with them was to streamline support for encapsulation.
So with properties in your code, you get the convenience and sort of the similarity to variables and instance variables, but you get the encapsulation of methods and all the power that goes along with key value coding. You know and love if you're an AppKit diehard user. One of the other things about properties is they're very easy to add to your code. They're very easy to adopt because if you've already written KVC compliant accessor methods, you just declare a property and the property will be implemented in terms of those accessor methods.
And here's the real killer feature. We support automatic generation of accessor methods when you use properties. We call these synthesized properties. And they're going to make a lot of the tedious code you may have to write, some of the code you saw earlier in the GC section, just go away.
And here's the real killer feature. We support automatic generation of accessor methods when you use properties. We call these synthesized properties. And they're going to make a lot of the tedious code you may have to write, some of the code you saw earlier in the GC section, just go away.
And here's the real killer feature. We support automatic generation of accessor methods when you use properties. We call these synthesized properties. And they're going to make a lot of the tedious code you may have to write, some of the code you saw earlier in the GC section, just go away.
And here's the real killer feature. We support automatic generation of accessor methods when you use properties. We call these synthesized properties. And they're going to make a lot of the tedious code you may have to write, some of the code you saw earlier in the GC section, just go away. Categories, and, gotta love 'em, Protocols.
The great thing about properties on protocols is they let you model both behavior and state. You can define a generic queue, for example, with generic nodes that would have next fields, next properties, and write some really interesting generic code. All of these classes, all of these forms, also have metadata generated for them that's available at runtime.
So you can walk up to a class that purports to implement a particular protocol or a category, all the metadata for categories is merged into classes, and walk up to a class and ask it, "What are all the things you support as properties?" And reflect on them, print them out, do interesting things.
So here's synthesized properties. First thing to note is something called a property attribute. Property attributes are directives to the compiler to say how to implement the property or how the property's behavior should work. So in this case, the Ivar property attribute tells the compiler to synthesize the implementation. It declares an instance variable, underscore name in this case. It declares and implements the accessor methods. So you write this.
[Transcript missing]
So here's how you add a property to existing code. I'm just showing this in context of an AppKit class. It already defines the title and set title accessor methods. So simply declaring @property in a string title gives the compiler enough information to let you start accessing that property using dot notation. You're done. You can even simplify it further and remove the accessors if you like. The compiler is, in essence, declaring those accessors when it sees this property.
So I was talking, I introduced property attributes by way of example with Ivar. We actually classify our attributes into two forms, those which are interesting to clients and those which are interesting to implementers, implementation details. And we try to keep the client property attributes in the interface file, in the header file, and try to move the implementation attributes where no one else has to see them. So the first three, the three client attributes I want to tell you about, one is the read-only attribute that allows you to declare this property can only be read, cannot be set. The compiler, if you say x.y equals zero, it will refuse.
We also defined three attributes that tell how to manage an object reference. One is by copy. This is appropriate for a key value coding attribute or by value, such as a string or an NSNumber. What happens is when you synthesize such a property, we will call the NS copying protocol methods "copy with zone" on the object when it's assigned into your object automatically. The complement of byCopy is byRef, which is default, and that just means use a retained pointer. And the third example is Weak. In the garbage collection system, Weak will give you a zeroing weak reference in your property. In non-garbage collection mode, you'll just get a non-retained pointer.
So implementation attributes. Technically the IVAR attribute is what we call an implementation attribute, although for architectural reasons it has to go in your header file today. That's because we need to know all of the instance variables that are in your class so that subclasses can infer how large your class is to do their IVAR layout.
In 64-bit, we'll actually be able to move the IVAR attribute outside of the header file. It's sort of an implementation detail. Clients of your property don't really care that it's synthesized or not. They just want to use it. The Ivar= form definitely belongs inside the @Implementation. And so what I haven't shown you is that you can declare, re-declare your property the same way you declare methods in your @Implementation section of your class. When you use Ivar=, your Italian compiler synthesizes the accessor methods in terms of an already existing instance variable. So it doesn't have to match a particular naming pattern.
You specify the name. The getter=setter= form, a good example of why you might want to use it is if you have a case of pre-existing accessor methods that don't match the compiler's default pattern, which is property name and set property name. Those are the default names for a property.
A good example is Boolean properties. Boolean properties, many of them begin with "is," although their setter is "set." So set name, is name. Auto display is a good example of that property. property in Ennis Window. Again, this is an implementation detail. No one's interested in seeing these sort of ugly-- what I would consider ugly directives in the header file.
Finally, the really exciting and, I think, innovative thing about properties, beyond what I've already said, is something we call dynamic properties. Dynamic properties are properties that don't have an implementation that the compiler can find at compile time. They are resolved at runtime. So the next section I'm going to go into more details about dynamic properties.
Dynamic properties allow a class, for example, to provide services to its subclasses in the way of The properties that it didn't know about at compile time. By using the property metadata, we can get the name of the property and we can get the type of the property. And all, in fact, all of the property attributes that I've told you about are all expressed in the metadata. So you can look at them, see if it's read-only or how it should be managed in terms of references.
So a good example for this would be Core Data. Core Data provides the NSManagedObject to do persistent storage of objects. And here's the canonical way, classical way of implementing a subclass of NSManagedObject. Let's say you have an entity that defines a property called "name" that's string-valued. In this case, The generic way of getting at your properties with core data is to use value for key.
Value for key is an untyped way to get at your data. You know it's an object. However, they encourage you to write your own accessors, and typically you'll use the Xcode assistant to generate code that looks like this. Another example of what I think is pretty awful to have to contemplate boilerplate code.
Especially if you want to add more properties and you have to deal with merging the changes into a source file you might already have. So wouldn't it be great if we didn't have to do any of this? Well, in the version of Core Data that's going to be shipping with Leopard GM, you will just be able to define a dynamic property, and Core Data will write the rest.
Turns out, with this implementation, Core Data can actually provide you an accessor that runs at least three times faster than that boilerplate code I showed you. The reason being, Core Data can access all of its private implementation details to give you the most optimized accessor. That's pretty cool.
So how does this work? Well, it's built on a new feature, a feature I invented for dynamic properties, but it turns out it's a more general feature. It's similar in spirit to forwarding. What happens is the runtime system, if it encounters a method, does a cache, has a cache miss, and it tries to find the method in your class, if it doesn't find it, it'll introspect on your class and see if you implement this new method, resolve instance method. There's also a resolve class method for class methods.
It calls the method if it exists, and if that method returns true, it's telling the runtime. It has, in fact, added some kind of method implementation to the method list, and that it should now try to find it. Now one of the interesting things beyond properties you could use this for would be to lazily map in, let's say, reflection to Python objects or Ruby objects that you might be using in embedded scripting language.
With this, you don't have to eagerly reflect everything into Objective-C as methods. You can simply do it as they're used. There are a lot of other things that Core Data is going to also be showing in their demo in terms of other kinds of key value coding implementations that this can be used for. So lazy and dynamic, that's pretty powerful.
So again, to summarize, properties are built on top of accessor methods. They provide really good support for encapsulation. So if you decide to change how a property is implemented down the road, you can, and clients are none the wiser. As a notational convenience, you can override property methods. You could override an individual accessor method if you like in a subclass.
If you're thinking properties, then you can use super.a as a notational way to get at the superclass's implementation. You can also use the name of a property unqualified in your methods, and that's just sugar for calling the accessor method. You can also say self.a if you like to be more explicit.
Finally, we made a conscious choice to implement chained or cascaded assignment with properties, so that if you do this x.a equals x.b equals c, the c is evaluated only once, and then it's more or less parallel assignment. We've seen other property implementations, for example, ActionScript in another program, Flash I guess it is, implements it in a different way. And it's very confusing to programmers because the value would get changed and it wouldn't work the way you'd expect.
So clients, well, if you've been to any of the core animation talks, you've seen properties. There they were. Stole my thunder. I was really excited to see that stuff. So I think the great thing you're going to find about them is that you can go find the properties. You can go find what these classes do. You don't have to rely on your documentation anymore. You can discover the stuff. That's, to me, pretty nice.
ImageKit as well is using them. And we're promised that by Leopard GM, Cocoa is going to be adopting properties very wholeheartedly. So go to the Core Data talk later on today if you can, or the Garbage Collection talk. I think they're at the same time. And now I'd like to hand off to Greg Parker, who's going to tell you about 64-bit Objective-C runtime.
Thank you, Patrick. We have a new runtime in 64-bit. We have a few reasons for doing this. We want to provide with a more extensible API. In particular, the existing runtime API. If any of you have gone and looked at the Objective-C header files, there are structures. There's a class structure. There's a method structure.
The structures we have are the same structures that we have to use internally because people go to see the IVAR structure, and they try and change the value, or they go to the method structure and try and change the method implementation. We don't want that anymore. So 64-bit, we're providing you API to do most of the things you're doing, most of the little tricks and hacks you've been doing, without needing the structures, which means we can change the structures.
When we change the structures, we can give you a few things. Better performance is a big one. We give you better memory performance. The class structure in particular needs to be initialized to start up time for all classes, whether you use them or not. This takes memory. This takes startup time. So, without a new class, with a hidden class structure, we can give you lazy class setup, better memory performance, better startup time, which means TextEdit, for example, doesn't need to initialize 2,000 Cocoa classes that it doesn't use as startup time.
Another better performance we're going to give you: better dispatch, object-C message send. We can do it better. You'll like it. Finally, in 64-bit, we can give you even more new features that would have broken the 64-bit compatibility. In 32-bit, we give you the API for the functions, but we still have to provide object-C class because all the existing programs still call it. 64-bit, we can take it away, which means we can give you some neat features, and we're going to talk about them in a minute.
So, the new API, new functions in the Objective-C runtime header files. Most of you will never care. Cocoa is a great object-oriented framework. For those of you who do care, you're writing a language bridge, you're doing some weird little hacks, you're calling class poseAs, for example. We have a new API for you. Functional replacements for structs. This is for obc class, obc, ivr, all the structures. We have covers that do most of the work of accessing the structures and changing the structures where reasonable.
Easier introspection of classes, protocols, in addition to properties that we've shown you today and some other things. We'll give you functional APIs. You won't have to go groveling through the header structures anymore to look at what an IVAR list looks like or what a method list looks like because we keep changing it, that sort of thing.
Finally, simpler class construction. For the very few of you who are offering new classes dynamically at runtime, it is a very difficult thing to do today. You have to allocate the structure, initialize the structure exactly the way the runtime wants. If you put a null pointer or the runtime wanted a pointer to a null pointer, it just crashes. We don't like that. We're giving you a better class construction API. Again, functional, no structures involved.
So these new functions are available now on your Leopard Seed in 32-bit and 64-bit. Again, all the 32-bit old API is still there in 32-bit. All the class structures, a couple functions we've taken away. 64-bit, the structures are currently still there, but not for long. You'll see in a minute. There's documentation on the WDC website talking about these new functions, describing what they do. So see that for more details.
Here's a quick example of a couple. Again, see the documentation. There are a few dozen like this. The first one, class gets superclass. This is a basic cover replacing the object-C class structure, which had a superclass field in it. Call the function instead. You'll be happier. We'll be happier, too.
Here's a quick example of a couple. Again, see the documentation. There are a few dozen like this. The first one, class gets superclass. This is a basic cover replacing the object-C class structure, which had a superclass field in it. So instead, we have this function, which will allocate an array for you, give you the count. The IVARs are now, of course, opaque pointers that you have functional access to as well. Finally, cost-get-property, copy-property list. Of course, looking at the new properties we've just given you and the introspection of the features that Patrick was talking about.
Here's a brief example of the class construction API. obc allocate class pair will allocate a class and its metaclass for you. Again, you don't have to know what the class structure looks like. Once you have that result, you call a few functions like class add Ivar to add an Ivar to the class. There's also class add method function, add methods as a class, similar things like that.
When you're done constructing the class, you call register class pair. It tells the runtime, first of all, you're done building it. Second of all, it should now be ready for use. Objects can now be allocated of that class. Much simpler than it was before. We can take down the old sample code that Arriba needed to copy and paste in order to get it to work before.
Second of all, it should now be ready for use. Objects can now be allocated of that class. Much simpler than it was before. We can take down the old sample code that Airbay needed to copy and paste in order to get it to work before. So all the existing class structures, obc class, obc method, the method list, the symbol table that nobody's ever heard of, these are all deprecated.
Selectors. Cell currently is a car star. It happens to be a unique car star, but it's just a car star. So a lot of people will cast it to a car star in order to do things like print it or stir comp or things like that. In the future, it will not necessarily be a car star at runtime.
So don't cast your cells to car star. Instead, call the cell getName function, which returns a perfectly good car star already. And that will remain, that will continue to work when cells are not car stars anymore. Here's a big one for you hackers out there: Pose as Class is now deprecated.
So there are complicated reasons for this. POSAS class is primarily poorly defined, unsupportable, unthreadsave, et cetera, ad infinitum. We are giving you functions in the new functional API that will do most of the things you used to do with PoseAs class. If you want to replace a method that's still called the old implementation, you can still do that.
You want to add a method to a class, you can use a category instead of a posing class to do that. So most of the functionality is still there, you just can't do class pose as itself. You want to add a method to a class, you can use a category instead of a posing class to do that. So most of the functionality is still there, you just can't do class pose as itself.
You want to add a method to a class, you can use a category instead of a posing class to do that. So most of the functionality is still there, you just can't do class pose as itself. and your leopard seed, they are not deleted. They're still there. They still work. If you want to get the deprecation warnings, you need to set your deployment target on the command line or in Xcode or wherever else to 10.5, and then you'll get standard compiler deprecation warnings for all the functions, all the structures, maybe even at-defs.
So, why are we taking away API? So we can give you new ABI. Why are we giving you new ABI? So we can give you new features. New ABI is not available yet. The current ABI is still the 32-bit one in the 64-bit world. In Leopard GM, we will have the new ABI in place.
The features we will give you at Leopard GM with this new ABI include non-fragile instance variables, zero-cost exceptions, and faster messaging dispatch. So, let's look at those. In Leopard GM, we will have the new ABI in place. The features we will give you at Leopard GM with this new ABI include non-fragile instance variables, zero-cost exceptions, and faster messaging dispatch.
So, let's look at those. It's got two IVARs in it, plus NSObjects IVAR, title and a content view. On the right, we have a subclass of NSWindow, your subclass of NSWindow, my window. So you're riding a pet store or something, I guess, because you have a kitten's array and a puppy's array, which are I-bars.
So here's the problem. Leopard ships, and they've added a new IVAR to NSWindow. Call it Toolbar. You want a standard toolbar API or something like that. So here's the problem. Leopard ships, and they've added a new IVAR to NSWindow. Call it Toolbar. You want a standard toolbar API or something like that.
So we don't like this. So today, AppKit, the rest of the Cocoa stack, any time they create a class, they may not add IVARs later like this, because any of your subclasses don't work anymore. This is fragile instance variables. In C++, it's commonly called fragile base class problem. C++ has it with IVARs and with methods, which makes it even worse. We at least have methods that work.
So we're going to fix this in 64-bit, non-fragile instance variables. How this works is the runtime is going to compute the offset at runtime based on the size of the superclass it sees at runtime. So your generated code is no longer having-- let's go back here-- when we look at the kittens array, it's no longer a fixed offset, 12, or 24, because it's 64-bit. Instead, that 12 will be looked up at runtime. And the Objective-C runtime is free to change it if it sees that NSWindow is in fact bigger than it was when you compile.
So what does this mean? It means that AppKit, Cocoa, everybody else can add instance variables without breaking your code, without requiring you to recompile your code. You can reorder the instance variables. You know, the offset is just a different offset inside the class. Deleting private IVRs also works. You still can't delete a public IVR because that's public API, really, once it ships. So, yeah, don't use public IVRs.
Private IVRs, you can delete them. You don't have to worry about the gap that is now in your class that previously you'd have to fill with some sort of placeholder. The performance cost is rather small. It just needs to read an extra offset from a global variable telling it where the IVAR now lives.
So this is the reason why @def doesn't work. Because when you call @def, it creates a compile-time structure that tries to look like the class as well as its superclasses. Of course, the superclass now may look different, which means the @def structure will be out of date. It just doesn't work anymore, so we're taking it away. Hopefully none of you care.
Finally, offset of an IVAR and size of a class also no longer make any sense because these are no longer compile time constants. You can use the runtime's functional API to get the offset of an IVAR. There's a class IVAR offset. There's a class get instance size. You want to know the size of a class object.
So you can use those instead of offset of and size of. So again, this is 64-bit Leopard GM only. The Leopard seed you have now is still using fragile instance variables, but they probably won't change apt-get between the seed and GM, at least not without you recompiling. So it'll be fine.
Next feature, zero-cost exceptions. This is primarily for those of you Objective-C++ programmers in the audience. So observation. Cocoa rarely throws exceptions. Most Cocoa exceptions are programmer error, out-of-array index access, things like that. Or method not found is another example. Currently, the implementation of exceptions in Cocoa is using set jump and long jump, which are slow when you enter a try block.
You need to do some work when you start the try block, whether or not you end up throwing anything. So instead, for the 64-bit API, we're going to reuse the C++ infrastructure and their online info for exceptions. What this means is a try block costs you nothing at runtime when you enter it. There's no set jump. There's no other data setup.
Throwing is a little bit more expensive because you need to do all the work that you would have done at the try block in order to find out where your data went when unwinding. But we just noted that Cocoa doesn't throw very much, so that's OK. Nobody minds. The big benefit for this-- again, Objective-C++ programmers out there-- your C++ exception handling and your Cocoa exception handling are now unified. You can throw-- Thank you.
You can throw a Cocoa exception and catch it in a catch-ID block or catch-dot-dot-dot block in C++. Conversely, you can throw a C++ exception and catch it in an Objective-C catch-dot-dot-dot equivalent block, or an Objective-C finally block if you have those. The finally block will be invoked instead of being just skipped completely like it is today. Which is sometimes wrong.
Finally, Faster Dispatch. You're going to like this. This is a sharp sample of Keynote showing the Objective-C message send overhead for the dynamic message dispatch. Dynamic message dispatch is great. It's powerful. You get key-value coding. You get forwarding. You get all this neat stuff that Cocoa uses. On the other hand, it does cost a bit of runtime. In this Keynote example, 15.2% of the CPU time is in message send, messaging lookup. It's not bad, but it's not great. We can do better than this.
Here's another example that we'll use for taking advantage when we optimize it. This is looking at the polymorphic messages that mail sends, the messages that have more than one implementation somewhere. If we look at the list, the top methods, there are about 10 of them, are taking 72% of the dispatch time.
So three-quarters of the time in OBSI message send is sent doing only a dozen methods or so. Looking back at the "for each" example, you'll note object index is the top consumer in mail messaging time. Looking at garbage collection, you'll also see retain and release are also top clients. So use that "for each" statement, the "for in" statement, use garbage collection, you can take away that overhead. But for the rest of the overhead, we want to be able to optimize this time.
So what we are adding is a hybrid virtual table dispatch. If any of you are familiar with C++ internals, you'll know what a virtual table is for doing dispatch. Objective-C message send uses a hash table. Looks up your selector using a hash function in the method table. A virtual table is basically an array of function pointers that you look up by index. So array access, hash table access. The array is a little bit faster.
In C++, this is a problem because they have the fragile base class problem. You can't change the Vtable. In Objective-C, we have a dynamic runtime. The dynamic runtime will generate the Vtable for you and will change the Vtable if the classes change. The other problem that C++ has is they use the virtual table for all methods in all classes.
This turns out to be a lot of memory. You have an array of function pointers for every class that covers all the methods that class has, all the methods that superclasses have. This is a lot more memory than the Objective-C system, which is only caching a few methods that you've actually called.
So we noted with the mail example that there were only a few methods that were actually the really hot ones, the ones that are called a lot, the ones we want to optimize. So what we will do with the hybrid virtual tables is we will only use the V table for the very frequently called methods.
These methods are, they're very frequently called, and they tend not to be very polymorphic. There aren't that many implementations of object-at-index out there. So what this means is the virtual tables are small. There's only maybe a dozen methods in them, usually. And the virtual table is generally the same for different classes, because your class didn't override release. It didn't override object-at-index, which means you can use your superclass's Vtable. So the Vtables get reused across many classes, which means that the memory cost is still very low.
For all the rest of your methods, the methods that aren't called as often, we still use the normal hash table lookup. So we don't get the speed gain, but those methods aren't called as often, so the speed gain is just not there. So the Vtable dispatch is at least 50% faster than the object-C message sent.
for those functions that you're actually calling. It is not quite as fast as C++ virtual tables because we want to avoid the fragile base class problem. So ours is a bit more dynamic. The virtual table size and contents are adjustable per app. So if your application has particular methods that it is calling frequently that you want to optimize, you stick those in a list in an Xcode window, and the virtual table will be constructed at runtime using your methods that you call. which means that you get the performance gain for the methods you write instead of just the standard foundation object and index and similar methods. So that is it for what we're going to say about the Objective-C runtime. And now back to Blaine Garst for some final words.
[Transcript missing]
All the features we talked about require changes to the runtime. And so, unfortunately, you're not going to be able to use these constructs if you're going to try to ship back on Tiger. There's just no way we could figure out how to make that happen. So, this is a Leopard-only set of features.
And the only consolation prize here, though, is that the headers that you use, even as we adopt these optional methods and protocols, as we put properties and stuff into the header files and stuff, you can still compile against 10.5 and ship against 10.4 or 10.3. So, you can look at them, but you can't use them until you compile and build for Leopard-only.
And so, with that, we have, again, a session on garbage collection. Stay in your seats if you want to hear more about that. We have a language lesson. We have a language lab tomorrow night. I will be -- there will be members of the team in the language lab probably most of the day tomorrow, in fact.