Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: wwdc2002-302
$eventId
ID of event: wwdc2002
$eventContentId
ID of session without event part: 302
$eventShortId
Shortened ID of event: wwdc02
$year
Year of session: 2002
$extension
Extension of original filename: mov
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: ...

WWDC02 • Session 302

Cocoa API Techniques

Cocoa • 56:22

Armed with supporting demos and code, this session covers a variety of API topics and techniques to help you better understand and leverage Cocoa. Topics include API conventions, techniques for extending Cocoa objects, class clusters, exceptions, plug-ins, and API performance and thread safety. This talk is aimed at both new and intermediate Cocoa developers.

Speaker: Ali Ozer

Unlisted on Apple Developer site

Transcript

This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.

Ladies and gentlemen, please welcome Manager Cocoa Frameworks, Ali Ozer. Good morning. Okay, this morning we're going to talk about Cocoa API Techniques. So what I mean by that is we're going to explore the techniques for Cocoa API design and conventions used in the Cocoa APIs. And we're also going to cover a lot of case studies to give you an idea of how these are used and how you can make use of them.

Our goals here are twofold. One, so that you can understand the reasoning behind Cocoa APIs, the reasoning behind the design of Cocoa APIs, and that way you can leverage Cocoa better. And secondly, that you can extend Cocoa objects and also use the same conventions and techniques in your own APIs that you can build into your own frameworks, so that there's a consistent set of APIs across frameworks moving forward.

Here are the topics we're going to cover. First, a quick look at naming and API conventions, then into object ownership, then into mutability, and we're going to discuss why you can't always change someone. Then we're going to look at subclassing and the dark side of subclassing, and then look a bit at plug-in design and performance of APIs.

First, naming and API conventions. Now, these are things that if you've looked at Cocoa APIs at all, you're probably familiar with, but I just want to put it up on slides so it's explicitly written down and you see it. Here are the kind of names we use in Cocoa. Let me just explain some of the conventions behind these namings.

First thing you notice in Cocoa APIs is we tend to use prefixes on class names, function names, etc. For instance, NSString, NSImage, NSPreferencePane. Those are part of the core Cocoa frameworks or extensions to them. And then we have prefixes like AB on our new address book frameworks, ABPerson, ABRecord, or DR for disk recording frameworks. The idea here is twofold. One, we want to protect against collisions.

Objective-C doesn't have namespaces, so this obviously reduces the chance of collision. Secondly, we want to differentiate functional areas. When you're looking at some source code and you see an NSImage, you know NS, this must come from Cocoa. This is as opposed to just seeing image where you might not know which functional area this belongs in.

That is why even in our Java APIs and Cocoa Java APIs, we tend to use prefixes, although they have namespaces. One rule we want to keep in mind is that we want to protect against collisions. Another rule we apply is it's better to be clear than brief. Even that means you get long names. For instance, here we use Objective-C's keywording facility to say insert object at index.

We name each argument with a noun. That's instead of using insert at, which is what's going on, or insert colon colon, which is a perfectly valid Objective-C name, but you lost something. Another example is remove object at index. That makes it clear you're passing an index. Remove object makes it sound like you're passing an object. And remove colon by itself is, you know, it's hard to tell what that's supposed to do.

Here is, as a bonus slide, the longest method name in Cocoa, at least in the public API. We've got some longer ones privately. And you know, you might think, my god, having to type that all the time is just not going to work. And this is what it looks like in code. Now compare this to if you didn't have the keywords.

Okay? And if you were just looking at this in your source code and you saw those 32s and 4s and trues and falses, I mean, you have to go open up the documentation, because you have no idea what those mean. Back to naming conventions. Use consistent terminology. If you use remove in some subsystem, it means remove or delete.

Continue to use remove. Don't use delete suddenly. Or don't, under any circumstances, use delete to mean something subtly different. Remove removes if it's there, but delete removes it if it's not there. That just gets lost in the noise. And although it might be tempting, don't open up the thesaurus and look for other words. Vaprize object and index is a great name, but it might not quite fit with the rest of the API.

Don't abbreviate names in the API. For instance, set floating point format. Maybe a bit long for what it does, but, you know, it gets the point across. You might be tempted a bit to point. That's not a very interesting word. Let's just cut it down. But, you know, somebody who's reading your API for the first time won't know what PNT might mean. Is it a pint or a point? Or, you know, don't fall into the Unix trap and forget your vowels. Set float put thumped. You know, that's just good, bad, ugly. You know, make it long.

If you do abbreviate, be consistent. In Cocoa, we do use some abbreviations, and we certainly use abbreviations where they're industry terms. It's OK to use them as long as you're consistent and predictable. Avoid names that are ambiguous. Send port. Are you sending a port, or is it the port used for sending? Display name.

Are you displaying the name, or is the name used for display? Center. You can verb anything. Is this the center point of something, or is it centering the thing? For instance, you can use human readable name, presentation name, or you can say center window. Try to be a little clearer when you have ambiguities.

Use verbs for methods which represent actions. Select cell, remove object at index. These do something so it's a verb. But if your methods return values, even if they're computed values, stick to the attribute or computed value name. Cell size, not calculate cell size. A value is being returned.

And as you can tell, in Cocoa, our setters use set, but our getters do not use get. So it's set color and color. Here we have a noun. Set color, color. With an adjective, set editable, is editable. Again, you know, we use is there. And with a verb, set draws background, draws background. We don't use get draws background or get is editable.

The one place we do use get is where we have multiple return values. Like here we have row and column being returned. They're related. It makes sense to combine them so you can have a get method with two arguments. This is the exception where we use get because here they're being gotten into those elements there. And note that in cases like this, often the values are optional, meaning you can pass and null if you don't want one or the other.

Now let me just quickly talk about some of the API conventions here. Be consistent again. For instance, in Cocoa we use floats to represent coordinates. We don't use ints. Similarly, whenever we have actually a coordinate in space, we use an NSPoint, which is a struct of two points, or two floats, I'm sorry. We don't ever have X and Y in the API. We always have NSPoints. And another example is instead of char star to represent strings, we use NSString objects. All this leads to, you know, this consistency leads to higher impedance matching.

If you have different types and different APIs, for one thing, you get performance issues. You know, converting between NSString and char star is not cheap, back and forth. And in addition, you might get lossiness. If you are converting between floats and ints every time you're calling some API or the other, you know, your floats are going to be lossy down to ints. We usually treat nil not as a valid object argument, although nil is a, you know, object. Object pointer meaning nothing.

Methods like append string, set title, and Cocoa APIs don't accept nil. It's because nil becomes ambiguous, and we don't want to go down that path of always having to document what it means. Similarly, if you ask a view, give me your subviews, and it returns an array. If it has no subviews, you get back an empty array because it's no subviews. And similarly, you can't put nil in arrays, dictionaries, et cetera.

Now, nil can be used to indicate runtime or other exceptional conditions. And, you know, for instance, if you try to create an object and it's not there or there's no file behind it, whatever, you know, you will get back nil. So those will usually be documented as nil being returned.

And finally, programming errors. Now here I'm talking about programming errors. For instance, you know, your array has eight elements and you try to index element million. Or, you know, you pass an invalid nil arguments. These are usually noted with exceptions, not by error codes. So in Cocoa, you know, programs, you'll see these exceptions when you're doing something wrong. And the idea is that you should fix those exceptions before you ship your app, rather than trying to catch those exceptions and deal with them at runtime.

Because they're programming errors, and, you know, they will lead to something bad if they're The most important API convention in Cocoa involves object ownership, and so I'm going to spend a few minutes on that issue. Let's see. This API convention... is object ownership is not transferred across calls.

Let me explain that. But to explain it, first let me give you a very quick refresher on memory management. If you were at the intro talk, you got this. And clearly, if you read any Cocoa book, you've probably seen this in early pages. Very quickly, Cocoa objects are reference counted. Alloc a new create new objects.

Copy copies an object. Those create reference count of one. Retain and release, add and remove a reference count. Auto-release releases an object later. And finally, objects are deallocated when their reference count is zero. So, you know, as far as ref counting systems go, it's a lot of work. So, it makes sense. Let me give you an analogy, not because you need one, maybe, but because I intend to milk this analogy. So, let's say you go into a restaurant, and there's a table.

You sit down. You now have that table. You've allocated it. It's yours. If you get up and leave, you've released the table, and the waiter can come and reclaim it, clean it up for the next customer. When you're sitting there, if a friend of yours comes in and you invite them to sit at the table, now there's two references to that table. Even if you get up and leave, the table is still owned, and the waiter won't clean it up. until your friend leaves as well, the table is still allocated.

The order release case can be thought of as, as you're leaving, you give the waiter a little tip and you say, hold that table for a while, and then you leave. So the waiter sort of lingers around, doesn't touch the table, and eventually cleans it up. But in the meantime, if somebody else comes, a friend of yours comes and sits at the table, the table hasn't gone away. So the order release just delays that release a bit.

With that in mind, passing objects around. Object ownership is not transferred across calls. When you pass someone an object, either by returning it from a function or as an argument to a function, they will retain it or copy it if they want and release it when done, and you don't copy or retain it for them. Another way to say this is, if you copy or retain something, you release it. So everybody is responsible for their own thing. The balance of reference counts in the universe must be maintained, and people are responsible for doing that.

And this leads to pretty straightforward programming in Cocoa. Here's a quick example. First, we get the title of a document. Then we capitalize that string. You know, we fix it up. Now this, you know, here, notice we're asking the string for a capitalized version. It returns a new string. And finally, we set that back as the document's title. One thing you notice here is you don't have to worry about allocations, retain, release, anything.

The code just falls naturally. You don't care where those objects are going, whatever. This is, you know, this is a good way to And you can also write this as one line if you want. You don't have to worry about error codes, allocation. Again, set the title. You get the title, you capitalize it, and you set it back as the title. Just one straightforward line.

Now, this brings us to the issue of the set title method. What should that set title look like? I'm going to talk about this a bit because there are many varieties of set methods and different people do them differently. I just want to give you the one true way.

actually maybe two true ways. This is a simple set method you can write. Assuming title is your instance variable, title is equal to new title. However, this assumes that the new title object will stick around and might not be appropriate in all cases. Here's another way to do it which fixes that problem. It copies the title.

This one has the problem that it leaks the previous title. It makes a copy of the incoming one but never gets rid of the first one, the original title. So if you call it multiple times, you will leak. Here's another version. It releases the previous one and it makes a copy of the incoming one. This one almost works, almost works.

But if title is equal to new title because somebody called set title with the same title, it will first release the title and it will try to make a copy. But in the meantime, it has been released. So this is still not correct. You can fix that problem with this version, which does work.

If the title is not equal to new title, it releases it, the old one, and makes a copy of the new one. So this one does work. So this is a good set method. Here's another good set method. Remember earlier we said auto-release? We can get rid of it.

Leave the earlier title but say get rid of it later and then you can make a copy of the new one. So even if the new one and the old one are the same, this one works because the old one is not released. So this is another good set method. So which one is best, the release or auto-release? Imagine some WWF music here. Which one is better? Well, the answer depends on what the get method looks like and what the usage pattern of the method is.

Here's a simple get method. You can just return the title, the instance variable. Simple. The question is, is it safe? Let me give you an example. Let's go back to our original example from earlier, change it a bit. We get the document's title. And then for some reason we save the document, assume that's a document object, and then for some, another wacky reason, we record the string into a log file. We say, you know, we saved this file.

So note that the string we get in line one we're using in line three. That's fine. This should work from a client of this API point of view. This is reasonable usage of stuff. Ali Ozer So the assumption is that the string is valid, but what if the save operation changes the title? Let's say the document was untitled.

As a result of save, a save panel was put up and it changed the title of the document. What if that freed that string that was returned to you in the first line? Here is the get method and the set method. In this case, we just returned the title and the get method. In the set method, we released the old one.

Clearly, these two violate the assumption from that previous slide. That's because the string you get back in that first line, the previous slide, will be released by this title release here, if the document's title happens to change. So these two are not a very good combination of set and get. But if you change this first line to retain auto-release, it becomes much safer.

What's retain auto-release do? Well, it basically extends the lifetime of the object. It still maintains balance of reference counts in the universe, because there's plus one, minus one. It's just that the lifetime is extended a bit. So if we go back to this slide, the string you get back in the first line will be valid by the third line, thanks to that. Okay, so this is one way to do it.

Here's another way to do it. You can... use our alternate set method and just return the title. This one is safe, too, because the auto-release method in the set method extends the lifetime of the title, and the one in the first line just returns that one. So these two are also fine. So the question is, which one is better? Again, we have now a complete get and set. Two ways which one is better.

Well, I think this one is better if the performance of your get methods is important. Because here, notice, the get method just returns the instance variable. No work done. That's it. So this one is good if the performance get is critical. This might be appropriate for low-level objects and so on.

However, in general, this is probably the safer one to use. And in high-level objects, you know, we try to use this, like app get objects. You know, if you're not getting things thousands of times a second, if you're not setting them a lot, you know, this is a good one. This is a good way to do it. One more note about this one is that it is thread safe compared to the previous one.

In this one, the return value has a lifetime in the thread where it was called. Because the order release pool it's released in isn't the thread that was calling it. While in the previous one, you don't have that guarantee. So if you want, if you're concerned about writing thread safe APIs, the get method, this get method happens to be safer.

Okay, so we have one more question to answer. Do you copy or retain? You know, we've been using copy on our strings. Why not retain? I mean, the answer might be obvious. Copy makes a brand new copy of the object and retains increments reference count on the object.

You walk into the restaurant, you see a great table with lobsters and shrimp, cocktail and everything. It's wonderful and there are people there. You have two choices. You can either go sit at that table, I mean I might be rude, but let's say you can. Or you can tell the waiter, make me a table just like that.

In the first one you're referencing another table. In the second one you're getting a brand new copy. So what depends on are you interested in the actual value object or are you interested Are you interested in the actual value? Are you interested in the value of the object? And again, let's say you have a method, the set title, which does a retain, like I'm showing here. And let's say you have these five lines of code. You create a mutable string.

You append something to it, so now the string's value is hello. Then you make that the title of document one. Then you append some more stuff to that string. Note that the document retained the string you gave it, so suddenly doc one's title has changed to hello world, which is probably not something you intended in this code snippet.

So the right way to do this is to use copy in something like set title, because when a title is being given to an object, the object probably cares about the value of the string, not about the actual string itself. Finally, neither copy or no retain. Is it OK for set objects to not copy, you know, or retain, just hang onto it, just like this? We showed this in the first slide. And the answer is yes, because there are certain relationships which don't imply ownership. There are examples in Cocoa. For instance, NSView. NSView has child views. NSView has a parent view.

NSView owns its children but does not own its parent. First of all, that would cause a circularity. And secondly, I mean, it doesn't make sense from a program design point of view. When you free a view, you can imagine the children view is going away but not the parent view going away. Similarly, an NSControl's target is not owned, a table view's data source is not owned, and the delegates of objects are not owned. These are all assumed to be relationships between similar entity-level objects that, you know, they don't own each other.

So it's okay to hold onto an object without retaining it, but you have to obviously take care when releasing things. If you gave somebody a delegate object and then you're going to free the delegate object, you should tell that first object to set the delegate to nil so it knows not to talk to it anymore.

Okay, so a little bit about mutability now, because we're talking about copy, retain, and such. Mutability means, mutable means editable or changeable. and some objects are by nature only mutable. The window object. You know, window object is changing all the time. It's moved around on the screen, it's resized, its contents are changing. Table view, same way. Now other objects, like value objects, can exist as immutable objects.

You can imagine a string which has been freeze-dried. You know, its value is hello world and never changes. That's much like constant strings in C. Or a color object. You can imagine a color which has certain RGB value, and it will never change. So it's possible to imagine these as immutable objects.

String and mutable string are two classes, which-- immutable and mutable. When you're operating on a string, you would use a method like string by appending string. Here's a usage. You basically append the string to the first one, but you get back a new value, because the original string hasn't changed. With a mutable string, you have a method like append string, which actually modifies the original string. Why have immutable objects? Seems like you can do everything with mutable that you can do with immutable. Well, one reason is performance.

Mutable implies that the object is able to change itself. If you have an 18-character mutable string, it has to be prepared to grow, shrink, etc. While an immutable string of 18 characters can have storage for 18 characters, that's that. Another one with performance is immutable objects can be shared better. If you send a copy to an immutable object, it can actually choose to retain, and you would never know the difference.

Simpler implementation. Obviously, if an object is immutable, it doesn't have to worry about changing itself. Its implementation is simpler. Thread safety. If an object cannot change, it's thread safe. There's probably still ways where it doesn't, but anyway, let's assume it is. I mean, it's usually the case. Thread safety is hard.

And also, easier analysis of program logic for somebody using your objects. If somebody has an immutable string in this line, and ten lines later they still have the same string, they know that its value didn't change in the background because it couldn't change. That's an invariant, you know. Immutable strings do not change.

So when would you use immutable? Well, across API boundaries, like our document object had set title and title. The arguments are NSStrings, not mutable strings. Similarly in implementations, the set title method doesn't maintain a mutable string whose value changes. Instead, it auto releases the previous title and it copies the new one. So there's no mutable strings here ever. The strings are just released and new ones are created.

When would you use mutable? Well, in APIs, when it's important to expose mutability. And in all of Cocoa, in all of the 250 or so classes and the thousands of methods, there's only one API that returns the mutable string -- that returns a mutable string. And that one is the backing store of the text system.

Clearly, the backing store of a text object can have millions of characters in it, and you don't want to edit the text system by creating new strings. You know, here's a million character string on append A, let's create a new string. So that's the one place where it's appropriate, and there might be others.

Mutables are strings or mutable objects are also very appropriate to use in a block. You know, let's say you want to build a string up by appending a bunch of stuff to it. Well, you don't want to use string by appending string over and over, because you don't care about those intermediate values.

You can just use a mutable string, build it up, then after that start treating it as if it's immutable. So that's so much about mutability. Now we're going to talk a little bit about subclassing and, as I said earlier, the dark side of subclassing. Subclassing is very powerful. It's one of the cornerstones of object-oriented programming.

You use it to create ISA relationships. Now, in Cocoa, it's used relatively sparingly. I'm not going to say-- I mean, it's used maybe less than other frameworks is what I really mean here. In Cocoa, some classes are meant to be subclassed, NSObject, NSView, NSCell. These are all abstract classes that you subclass. And other classes can certainly be subclassed. But often or usually, things are not subclassed. For one thing, when we have classes like NSObject, NSView, we've already given you a lot of interesting subclasses.

Another, there are other ways to achieve, to extend Cocoa objects than subclassing, and I will talk about those. And finally, subclassing just sometimes tends to be misused, abused, just used the wrong way. And let's look at examples of that. People confuse Hazza or user relationships. Every person has a name. Names are represented by NSStrings. Why don't we just subclass NSString to create a person object and add a bunch more variables? This is nifty because you can do a person is equal to the string Joe. That looks like it would work.

Ali Ozer I mean, although this might be tempting, this is just bad from an object-oriented design. And if you started using this person object in your program, pretty soon you'd realize some of the problems. Ali Ozer You know, string has a length. It returns a number of characters. But what does length on person do? And I won't even go there. But, you know, so it's... There are these ambiguities.

Similarly, you can subclass animal to create a person. This might be appropriate in the context of a taxonomy program, but if you're creating a database of people records, if you're creating an address book API, person should not be subclass of animal. There are many things about animals you won't care about in that context. Similarly, you might think your roommate is a pig, but you wouldn't subclass pig to create a roommate object. It's just not the right relationship.

Sometimes People create subclasses where you probably want attributes or parameters instead. For instance, a text field you can type into. Often in program flows, text fields can become editable, noneditable. I mean, good UIs do this. You know, sometimes a text field should not be edited. Sometimes they should be. If you had two separate classes to represent those widgets, editable text field, noneditable text field, every time the text field changed status, you'd have to deallocate the previous instance, create a new instance, and, you know, it just gets a little messy.

So something like this would be managed better by setEditable on the text field object. Now let me give you an example out of Cocoa itself. We have a string class, as I already talked about, and we have an attributed string class. Many people, when they first start looking at Cocoa, think attributed string is a subclass of string.

String is a sequence of characters, characters and length. And an attributed string is a sequence of characters, characters and length, but also has attributes, text styles. And this is why people think an attributed string is simply a string plus a little more. Let me give you an example where that subclassing would just fall apart. You have a string, you have an attributed string. Let's say the values are Joe, and the attributed string also happens to be blue.

If you ask the string, are you equal to attributed string, it will say the characters are Joe, the length is three, yes, we're equal. If you ask the attributed string the same question, it will compare the characters, the length, but then it will compare the color and it will say, hey, the color of the other string, you know, it doesn't have it or it's black or whatever, and it's not equal. So you have a situation where A is equal to B, but B is not equal to A, and that violates all sorts of laws of physics and mathematics, so it's not a good thing to have.

Another reason why subclassing attributes from string would be bad is due to lack of multiple inheritance, you know, the inheritance tree becomes messier. We have string, mutable string. We have attributed string and this mutable attributed string. Which one do we subclass? Let me talk about some alternatives to subclassing. Categories, if you were in the intro talk, you already heard about this a bit. Categories allow additional methods to be added to other classes.

and all instances get these new methods. So it's not like subclassing where only your subclass gets these new methods. This is a language feature and this was actually added to Objective-C as a way to break apart large source files. Who wants to have nswindow.m that's 100,000 lines long? You want to break it up into multiple source files. But categories since then has been used for more and more stuff and it's actually very useful.

Here's an example of a category. As you know, NSString sits in Foundation Kit, which doesn't have any UI level functionality. But if your program links against App Kit, NSString gets a few methods, one of them for instance drawAtPoint. It's because the application kit knows how to draw strings, so it adds this functionality to NSString. And anybody using NSString, to them it looks like string has this functionality out of the box. And similarly, your programs can add other functionality to NSString as well. And everybody, all the strings in your program will get this.

Delegation is another alternative to subclassing. It allows an object to act on behalf of another. It's not a language feature, though, unlike categories. With delegation, classes explicitly support delegates. They have methods like setDelegate and delegate. And this is basically the object that this object will refer to in some cases.

In AppKit, NSWindow, for instance, has a delegate method, and it asks this delegate questions like, window should close. When the user hits the close box, the delegate is asked this question, and the delegate can return no if the document is not saved, for instance, if the delegate was a document.

Or window will resize. Let's say you want window to resize by increments of 10 or 20 because there's some grid in your window. The delegate would handle this by getting the proposed size and returning a new size for the window. So you don't need the subclass, you just implement a delegate.

Notification is another alternative. This is sort of like delegation, except it allows happenings. I'm using the word happening rather than event, because I don't want to confuse event, which is already overloaded. It allows happenings to be broadcast to a set of unrelated observers. And there's more than one, unlike delegates.

So the observers observe, but they don't interfere. This is also another language feature. This class called NSNotificationCenter can be used to use notifications, and classes declare what notifications they post. For instance, NSWindow has a bunch of notifications. Two of them are windowDidResize and windowWillClose. These would be sent when the window is about to close or when the window just resized. are also usually sent to the delegates as a courtesy.

Now, if we were to go back to our restaurant analogy, the waiter knows how to deal with customers a great deal. But if something were to happen, like the customer finds a fly in their soup or they just fall over and die, the waiter probably wants to go find the manager of the restaurant and let him handle these situations.

But whenever a customer leaves a table, the waiter probably just goes to the back, to the busboys or whoever cleans the tables, and says, "Hey, table 17 opened up." And that's more like a notification, because one of the busboys will come and clean it up and the waiter doesn't care. He just announced that table 17 just cleared up.

Okay, so instead of subclassing, when you're looking at a Cocoa object, see if the class has the delegate methods or notifications that you can use and use them instead. Or see if you can add a category method on the class to achieve the same results. Now category methods, as I said, will give the same functionality to all your objects, and maybe that's not something you want. But anyway, often category methods will actually get you what you want. And what do you do if you must subclass? Now let's talk about this situation.

Well, the answer is simple. You just subclass. Unlike other languages, which have the ability to prevent subclassing, in Cocoa, there's no final keyword or anything, and you can subclass any object. And sometimes people subclass the weirdest objects that we never thought you would have to, because there was a bug in it, in the implementation, whatever. So just subclass.

Of course, when you're subclassing, You know, know what the primitives are, know what the designated initializers are. Now, designated initializers, I'm not going to talk about it, but it's basically the primitives for creation. And the one more interesting thing you need to know about is whether the class is a class cluster. And this is some, this is an area which trips new Cocoa programmers up. So I'll talk about it.

I'll talk about NSString and how it looked a few years ago. Well, it didn't look like this. It actually looked like this. And of course, this was a nightmare if we-- this is what we revealed to you. This would be a nightmare both to you and it would be a nightmare to our TechPubs folks who have to write about this. So suddenly they have to, you know, document this many classes. These are all different implementation classes that know how to deal with different kinds of strings. Luckily, we decided to make most of this private.

So all you saw was an NSString and an NSMutableString. And it was a good thing too, because recently we changed this hierarchy to look like that. Clearly if we had to expose all those classes, we'd have to be schlepping them around in the frameworks because there'd be people using them for some reason or another.

The idea here is that with class clusters, there's a collection of classes. These are implementation classes, but they're hidden by an abstract superclass. And you don't see the implementation classes, because abstract class manages them for you. It returns the right subclass. One problem here is that these implementation classes are not available to you.

So if you want a subclass NSString, you usually have to provide your own implementation, because NSString is abstract. And the primitives need to be clearly defined for you to do this, because there is no implementation. This is not like NSView or NSWindow, you know, which is either semi-abstract or, in the case of Window, fairly abstract, fairly concrete class.

[Transcript missing]

Okay. So, so much for subclassing and class clusters, and, uh, Now I want to talk a little bit about plug-in design. We were actually going to have a whole talk about plug-in design, and then we decided that plug-in design in Cocoa is way too easy, and a whole talk just wouldn't be right. So we decided just to have a few slides on it.

In Cocoa, when you're designing plug-ins, you want to package your plug-in as a bundle. And you already probably know what bundles are. They're those folders that look like single files in Finder. And you would use the NSBundle class to load it. And typically you'd put objects, your own classes in there to do various interesting things, and you would get the main class with principal class, and you'd expose the entry points via this principal class. And that's all it really takes. And I'm going to try to show a demo to see how easy this is.

So exposing entry points, there are two ways to do it. There are actually multiple ways, but two, you know, two ways I'll talk about. One is you have a superclass meant to be overridden. And in fact the various bundles, the various plug-ins that we provide in the system use this technique. You would supply the superclass implementation often in the framework.

For instance, the screensaver bundles for our screensaver engine are subclasses of this object called screensaver view. You just subclass that and provide your own custom screensaver. Preference panes are subclasses of this object called NS preference pane. Again, you subclass that and do custom preference panes. And these are available on the system if you wish to create, you know, plug-ins for these. Another technique is to use the object -- use an Objective-C protocol.

Protocols gather methods together with no implied class hierarchy. So there are a bunch of methods and that's it. And a class which conforms to the protocol must implement all the methods. And there's no base implementation needed. So you can just say plug-ins, you know, for my application should just implement these eight methods and that's it.

And you don't care how they implement it. They can be subclass of an object, they can be whatever. You don't care. Here's just a simple, you know, sample plug-in. For instance, you might have an image processing plug-in which has the three methods, process image, can process image, and description. And that's that.

Okay, at this point, I have a little bit of time, so I will show you a demo of how to do this. Just show you, do it on the, on the fly here and let's see if it works. Can we switch to this demo machine, please? Most of you probably know TextEdit. This is a pure Jaguar system here. And here's TextEdit. So what I'm going to do is I'm going to add a plug-in model to TextEdit and write a plug-in for TextEdit.

Here is TextEdit's controller. And I'm going to add the plug-in model initialization here. Now, as I said, one way you do plug-ins is to define a protocol that you expect your plug-ins to implement. And I'm first going to do that piece. And I'm going to be using the demo assistant to do my typing here.

So there's the protocol I want to implement. Notice it's very simple. All it has is prepare with menu. That's because I'm going to tell my plug-in, here's the menu you should insert your stuff into, and that's it. You're on your own. The next thing I'm going to do is enumerate the plug-ins that I have. And to do that, I'm going to write a new method here.

load plug-ins. And this is really the meat of the whole thing. First of all, I'm going to have a plug-in menu, which is nil. Then I'm going to look at my plug-ins folder, You can choose to load plug-ins from any place you want. You can either load them from standard places like libraries/texas plug-ins. You can load them from the system.

In this case, I'm going to load it from inside the app. is a developer of the Cloud Platform and is the founder of the Cloud Platform. He is the I'm going to get an array back which enumerates the contents of that folder. I'm going to look at the count of that.

and I'm just going to start enumerating. Note that if there was nothing in that folder, the count was zero, this while loop just exits. I'm going to get the path. So now I'm going through all the bundles. I'm going to get the path to each one, and I'm going to create a bundle with it. Note that this might fail, in which case I don't do anything there. But this line here basically creates an NSBundle object for that plug-in.

If I did get a plug-in, then I create an instance of the principal class. Note, this is the way, this is how we're using the protocol. We're saying that this is a class of any object, I don't care, but it must implement the protocol. And we get the principal class of the bundle, we alloc an instance, and we initialize it.

Pretty straightforward. Now one more thing we need to do at this point is because we know we have a plug-in, we should build our plug-in menu. If our plug-in menu was nil, we create a menu and we add it to our application. And there are several ways to do this.

You can choose to always have a plug-in menu, which is empty, or you can choose to create this in interface builder and have an outlet to it, or you can choose to do it in code here. And just to show you the code here, I did it here. This basically creates a plug-in menu, adds it to the menu bar of the application.

And the next thing we do is we tell that object from this line here, is the first to prepare itself with that plug-in menu we just did. And note that this will loop through all the plug-ins in that directory, and that's really it. And the last thing I need to do is, when the application is launched, go find all its plug-ins. So let me build and run this.

So here's TextEdit. One thing you'll notice, there's no plug-in menu. That's because we don't have any plug-ins yet. Let me quit. And let me miniaturize this. So now to create a plug-in, And I'm going to create a plug-in from scratch. You create a Cocoa bundle. And look, the name And I'm going to call it enlarge.

Somehow I knew from some previous run. This is interesting. I will actually, because I want to start this from scratch, let me quit PB and do this. Okay, new project, new Cocoa bundle, and I'm going to put it onto the desktop. And I'm going to call it enlarge.

And by default, plug-ins have just the main.c and not much more. I'm going to create two little files here, Objective-C, class, and the header file. I'm going to call it enlarge. Now the first thing is enlarge.h, the interface file. Here what I want to do is I want to basically tell this thing that it implements that protocol.

So I'm going to import Cocoa.h, because I want to use more of Cocoa than just foundation. Then I'm going to put the protocol here as well. Typically in a real project you might have the protocol in a shared header file as opposed to duplicating it here, but for the sake of demo I'm going to do it here.

And finally, you would go ahead and declare that the object implements this protocol. So that's it for the header file. Let me save this. Let me switch to my interface file. And let me put, to my implementation file, and let me put some implementation for enlarge. This turns out to be fairly easy.

We implement prepare with menu. That's all we have to do. And all we do is add to that menu the menu item for this plug-in. In this case, the title is enlarge. We even localize it. The selector it calls is enlarge colon, and there's no key colon, and this is the target. That's all we have to do to implement our plug-in, and we return yes to say we succeeded. And then we implement enlarge.

And that's all the code required for Enlarge. Let me push that to the top of the screen. The goal here is, what Enlarge does here is it just looks at the main document in the app and then it grows the font to 36 points. That's it. And this is the whole plug-in. No other code is needed. So let's save. Let's build.

That's done. Let's hide this. So here's our text edit and here's the bundle we just built. I'm going to open text edit, package contents. I'm going to go into its contents folder and I'm going to create a new folder here called plug-ins. This is the standard plug-ins location for an application.

And Finder also knows how to manage this, by the way. There are menu items in Finder to do this. I'm going to create my bundle into this. I'm going to copy my bundle in here. Okay, and let's run TextEdit again. There you go. It's got a menu item, so I can type. And if I go to my plug-in and use it, it just enlarges. There you go. We've just added a plug-in model to TechSend.

And in case I want another plug-in, let's see, here is one I had created earlier. This one is slightly more sophisticated. It allows you to go to any line you want. I'm going to copy that plug-in here. Let's run TextEdit again. There's go to line. So if I type some text, line one, line two, line three, line four, let's take advantage of our enlarge plug-in. Then let's say go to line.

A go to line panel pops up, which is part of this plug-in. I'm going to type three, and it goes to line three. And again, the possibilities of plug-ins are endless here. The go to line plug-in ends up being not much more complicated. It again has a prepare with menu.

But because it has a panel, it connects it to show UI. Show UI simply loads a Nib file, and it puts up a panel that comes from that Nib file. And the go to line implementation is here. takes advantage of finding lines in the string to simply go to the desired line.

Okay, that's it for the demo. Thank you. One performance tip in plug-ins. What I did there was probably not very good from a performance point of view, because we non-lazily loaded all the plug-ins. You know, we went ahead, we looked at all the plugins, we created instances of them, we added them to the menu all at startup.

If you had 30, 40 plugins, it would seriously impact TechSense's launch time. So typically what you should do is try to make it so that the plugins are not all loaded at once. One way to do that is to provide the information for the plugin in its Info.plist. For instance, in this case, all we really need to know about the plugin was what to put in the menu. So maybe that can be put in your Info.plist. And only when that menu is tickled would you go load the plugin.

You can also put in some other file. If you have more initialization, you might just want to put in another file in the bundle rather than the Info.plist. This allows you to load the bundle and execute the code lazily. And then, you know, you can have hundreds of bundles and the launch time will probably not be impacted. So talking about performance, let's talk a bit about performance of APIs. Oh, my water is there. So, performance of APIs. Here is an earth-shattering fact: as fast as possible, that's good.

There happens to be many conflicting constraints. Do you reduce memory usage? Do you optimize for CPU usage? Do you try to find a middle road? You know, it's hard to optimize everything at once. So in APIs, it's interesting to indicate what the performance characteristics of the APIs are. And there are, you know, several ways to do this. Now, one thing you can go ahead and say is, 20 nanoseconds per probe. You know, I can do object at index in 20 nanoseconds. Well, that's interesting, but it doesn't give the whole story.

Behavior over a range of input parameters happens to be also interesting, and sometimes it's in fact more interesting. Let me give you an example back to our restaurant. You go into the restaurant, you sit down, you order a pizza. It comes in 15 minutes. That's good. What if 20 people came into the restaurant, sat at all the tables, and they all ordered pizza? Well, the kitchen might have a harder time dealing with that. Maybe they only have three pizza chefs or whatever. So it might take longer than 15 minutes for people to get their pizza, as people are scrambling.

And here's another case. You go into the restaurant, and you want to play a prank on your fraternity buddies, and you order 10,000 pizzas. Well, the kitchen is certainly not prepared for that. And what's going to start happening is people are scrambling back there. There are going to be pizzas everywhere.

They're going to start swapping the pizzas in and out of the sidewalk, bathrooms, wherever they can put them. The waiter is going to be running in and out with pizzas. And as a result, the waiter might turn into a big spinning pizza pie. And in fact, that's where the weight cursor came from.

Anyway, the point here is that just because something takes a certain amount of time under different stress conditions, it's not clear how long it will take. And this point here, behavioral range of input parameters, is interesting for level of objects such as NSArrays, NSStrings, and the kind we provide in Foundation. Let me talk about NSArray as a case study. The following APIs in an SRA, for instance count, object at index, add object. We expect these APIs to execute in constant time. We don't expect them to be dependent on the number of objects in the array.

When the array has ten objects and you ask count, the time to return the results shouldn't change over that ten million objects. And the same is true usually for object at index and add object as well. Now, note that add object adds object to the end of an array.

Now, that's not true for all methods in NSArray. If you were to insert an object in the middle of an array, we often say that that's going to be slow. Or it's going to not be necessarily slow, but it's going to be depending on the number of objects in the array. Because if you had 10 million objects and you insert an object in the middle, it might have to push, you know, 5 million objects this way or that way to make room.

Now that's because we're telling you that NSArray is not a linked list or it doesn't have some other weird implementation. It's more like a C style array. And if you do create a linked list, do not make it a subclass of array because it has different fundamental performance considerations.

Make it a linked list class that's not related to an SRA. You can give it a similar API, but don't call it an SRA. One question you might have here is aren't you giving away implementation details here and isn't object-oriented programming supposed to hide this? The answer is yes, but in low-level APIs where performance is important and clients will depend on you, clients will build subsystems that use these a lot, the performance becomes even a more important consideration. NSDictionary is another good case study. NSDictionary, for those of you who don't know, it stores key value pairs. It hashes them. So it's like maps, for instance, color to blue, font to Helvetica. There's a key and a value. And it uses hashing to store this.

So what this means is in NSDictionary, the following methods often execute in constant time. Object for key. No matter how many objects you have in the dictionary, it will return in constant time. Similarly, set object for key, or even remove object for key, are usually expected to execute in constant time. However, NSDictionary relies on the following NSObject methods to do its thing. It calls isEqual and it calls hash on the objects that are in the dictionary.

And these can be overridden by subclasses. So depending on the overrides, you know, NSDictionary's performance can change a great deal. Similarly, and the goodness of the hash function also has a big impact on NSDictionary performance. In fact, it has a much bigger impact than, you know, just the overall performance of these methods. What I mean by goodness of the hash function is the ability for the hash function to return values that are spread out.

If your hash function always returns a single value, like 0 or 42 or, it doesn't matter how interesting the number is, it's always a single value, the dictionary will start getting a lot of conflicts in its hashing, and as a result, its performance, the worst case performance, you know, will be reduced to linear searches and insertions and it just, you know, will start acting very bad. So typically when you use NSDictionary's, one thing you should watch out for is whether the hash function you have is appropriate for the data you have.

And often, you know, you have a lot of hash functions that are not as good as the hash functions that you have. So typically when you use NSDictionary's, one thing you should watch out for is whether the hash function you have is appropriate for the data you have. that is the case. You know, like, strings have hash math functions which work well, numbers have hash functions, so on. So often it will work, but this is something to keep an eye out for.

And here's one little tidbit. If you do implement is equal and hash, one thing to keep in mind is for the purpose of this dictionary and other Cocoa objects which do hashing, is equal implies that the hashes are equal as well. If you break this variant, your dictionaries will just start going south.

The other case study is String, and this is another interesting point here. As we talked about earlier, it has the primitive method character at index. This simply returns a character at a certain location. Now, this method often is very, very fast. It just, you know, whatever, two nanoseconds, whatever the numbers are, but it's very fast.

In fact, it's so fast that the invocation overhead, the overhead of actually calling the function, can be a significant overhead. You know, the function call takes maybe more than just returning the character. So one thing that's usually a good idea in NSString is to use the bulk method whenever possible.

What I mean by that is it's this method where you give it a range, and it returns to you, you know, multiple characters. So -- and you give it a buffer as big enough to hold that range, and you ask for multiple characters. It turns out if you're asked for more than three, four characters, it's usually a better idea to call this than to call character index many, many times, because the overhead of that call is bad. And one more point here is that if you do subclass NSString, it's often a good idea to also override this method.

Although this method is not a primitive, for performance reasons, overriding this method will make your string subclass. Subclasses work much better. And, you know, this is sort of like going to Costco and buying the 100-pound bag of keto litter as opposed to buying many small bags. You know, you -- the overhead becomes lower because, you know, the cost is less and all that. It's very similar.

Well, you know, that's as far as I want to say about this. So my goal here was to, you know, give you an idea of some of the Cocoa techniques and conventions so you're familiar with them. And hopefully, not only can you use these in your apps, but, you know, we'd really like you to create your own frameworks, your objects that you can sell or share with other developers.

And if you, you know, follow these guidelines, follow the other Cocoa guidelines, you know, this is not all. If you follow them, you know, hopefully your frameworks and your friends' frameworks and developers across the country, their frameworks, they'll all fit in like Lego pieces with high level of impedance matching.

And, you know, it will work very well together. One more thing I should note is, you know, you look at these guidelines and if you go look at the Cocoa APIs, you might find a few places where we violate these guidelines ourselves. And that's natural to be expected.

These guidelines have been developed over, you know, the last ten years, and Cocoa has been developed over the last ten years. And there are certain areas and certain areas that we're not familiar with. And so, you know, we're going to be looking at these guidelines and we're going to be looking at these guidelines and we're going to be looking at these guidelines and we're going to be looking at these guidelines and we're going to be looking at these different areas in Cocoa which, you know, were developed before the guideline was developed. You know, those bad method names I gave, ambiguous ones, they all happen to be methods in Cocoa, for instance, that we'd like to get rid of someday.

Now, one hour talk isn't enough to cover all the topics and all the API areas and so on, but here is the Apple documentation. There are many programming topics that are described conceptually, and that's very good. There are some good other books out there. Clearly, learning Cocoa and building Cocoa applications, if you're just diving into Cocoa, those are good books.

Aaron Hillegas' Cocoa Programming for Mac OS X is also a very good book for beginners. I recommend it highly. If you're interested in API, object-oriented design issues, etc., the book by Eric Gamma and three other folks, Design Patterns, it's an excellent book. It actually is about five, six years old, but it takes some of its examples from Next Step at the time and has many other cases from other environments.

In the roadmap, the intro and what's new, you missed those, but you can catch them if you wish. Later today and tomorrow, we have various talks that cover various areas of Cocoa, scripting, controls and accessibility, drawing, the tech system, and finally, the feedback tomorrow afternoon at 5. Okay, and that's it. Thank you.