Essentials • 1:14:06
Mac OS X users expect more than merely a stable, functional application. Learn how to make your application really stand out by improving usability, responsiveness, security, internationalization, accessibility, and operating system integration. Delight your users with the attention to detail that sets a great application apart.
Speakers: Doug Davidson, James Dempsey
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Good afternoon, everyone. I'm Doug Davidson, and I'm here to talk to you about polishing your Cocoa application. So we all know that Cocoa is designed for rapid application development. It should make it easy to get your application up and running. But does that mean you're done? Well, no. Usually what it means is that our customers just increase their expectations. But the good news is you're going to spend your time not so much on making your application run as on making it run great.
And that's going to be our theme for today. We have a number of topics that we're going to talk about today. We'll talk about 64-bit. We'll talk about performance, responsiveness, security, localization and internationalization. We'll talk about usability and about scripting and accessibility. And now that happens, I have a little sample application that we're going to be working on. It's called Zip Browser.
And what this little app does is to let you look inside a Zip archive without having to unarchive the whole thing. Now, this is not available as sample code just yet. We're going to try to have it out as soon as possible. Look for the Zip Browser example. So I'd like to show it to you. Let's go over to the demo machine and take a look.
[Transcript missing]
I'll show you an action. Open something up. Let's pick a zip archive and open it up and there are the contents. And you'll see we're using an NSBrowser to display the contents of the zip archive. Now, those of you who have been using Cocoa for some time might remember that NSBrowser is one of the first and oldest of the Cocoa controls. And in recent years, it's maybe lagged a little behind some of the more modern controls like the Table View and Outline View. Well, no longer.
In Snow Leopard, we have added a new set of methods to NSBrowser that we call the Item-Based API for NSBrowser. Makes it really easy to use NSBrowser. Makes it a joy to use. And that's a good thing because with NSBrowser, you get a ton of functionality for free. You get things like expansion tool tips. You get the arrow key navigation. You get type ahead. And let's take a look at this.
You get this little preview. This is the preview column here. With NSBrowser, all you have to do is provide the view that does the drawing in this. And NSBrowser does all the management of bringing it in and out. And here we've chosen to show a little bit of information about each entry in the zip archive, just an icon, a name, and the sizes within and out of the archive. OK. So here's our application. It compiles. It launches. It runs. It's a little bit of a mess. But it's a lot of work. We have a lot of work to do on this app today. So let's take a look at some code.
Let's go into Xcode. Now, this is a very simple document-based application. As it stands, it's about 200 lines of code and has three custom classes, a model, a view, and a controller. And what we're looking at right now is a header for our model class, zip entry, which models an individual's entry in the zip archive, and it stores some basic information about it, a name, a location in the archive, the sizes, and so on.
And because there's an implicit tree structure to these things that's used to store directory hierarchies, we have leaf and non-leaf entries, and the non-leaf entries have an array of child entries. They're all very simple. So let's take a look at our view class. And our view class is the class that does the drawing for that preview column in the browser.
And again, it's extremely simple. In our -- see, in our draw rect, we use very standard Cocoa methods to take this image that we got from NSWorkspace -- it's an icon -- and to draw it, and then to take these various pieces of text and to draw them, and there's a little extra code to calculate the sizes so we can lay them out. Perfectly standard Cocoa view techniques.
And then our model -- because we're using a very simple document-based app, we just have a subclass of NSDocument that acts as -- I'm sorry -- that acts as a controller for everything. And so the main thing we have here is the read from URL method that gets the contents of the zip archive, goes through it, looking for all these entries, creates the model objects, and adds them to us.
And then I mentioned about the item-based API for NSBrowser. So let me show it to you. Here it is. This is the complete -- this is This is all we do here as the delegate of the browser. I note, what is it, six one-line methods. That's all we have to do, and this browser does all the rest.
So what is it that we want to do to this application? So the first thing I'd like to do is make it 64-bit. Now, if you have been waiting to make your app 64-bit ready, now is the time. 64-bit is a big emphasis in Snow Leopard. You heard Bertrand talk about it.
He explained why you want to do this. Now, of course, we're not making our apps 64-bit only. We need them to run as 32-bit on 32-bit machines, 64-bit on 64-bit machines. So what is it that we do for that? First of all, make a copy of your project.
Then we have a script. In Snow Leopard, we have a new Ruby-based script that you run on all your source code and that does some straightforward substitutions to do the conversion. And then you take a look at all the differences between the original and the modified version. Yes, you have to look at all the diffs. Now, I've already done that for this project. So let's take a look at some of those diffs.
What sort of things are you going to see? So the most common sort of thing you'll see is that the script has replaced 32-bit integers, signed or unsigned, with things like NSU integer and NSInteger, which are the Cocoa integer types for integers that are 32-bit on 32-bit and 64-bit on 64-bit. And usually this is exactly the right thing to do.
In most cases, this is just fine. But I've been looking through this, and I noticed there are a few places here where these values actually represent things that I am getting out of the zip archive that are defined by the zip format to be specifically sized quantities, either 16 or 32-bit. Now, it's not wrong what's done here. I certainly could use NSU integer for those. It's plenty big enough.
But when I was looking at this, I decided that it would more accurately represent the intent of the code if I used fixed-size types for these. So I chose to use uint16t and uint32t. Maybe I should have done that from the beginning, but in any event, now is a good time to make that change. What else are you going to see? Here's another common thing where you run into printf-style formatting integers. Now, the script tags these cases because printf-style formatting doesn't really play very well with variable size types like NSInteger and NSUInteger.
So you need to look at these and do something. And what I'm going to do is something that's fairly straightforward, sort of brute force, but what I'm going to do is take the arguments, these integral arguments, and cast them to something that I know is big enough and that I know printf-style formatting will understand, usually a long or a long long.
And I've made those changes, so let's look at the changes that I've made after the script. So here in this case, what I've done is to take the %u and add an l quality. So I've added a modifier to it, make it long, and then cast the argument to an unsigned long to make sure that the printf-style formatting will understand it.
In the other cases, I looked at through it and decided which ones should be Uint32T and which ones should be Uint16T and replaced those uniformly throughout the project. So let's take a look at that updated version after 64-bit. Let's try running it. See if we can open up a zip archive. And there we go. We're writing in 64 bits. Now let's go back to the slides.
And let me reprise those steps for 64-bit Redius. First one, make a copy of your project, run the script on it. Here's the location of the script in Snow Leopard. Then examine all the diffs and approve, disapprove, or modify them. There are some things that you need to keep a special eye on. As we saw, uses of printf-style formatting may require some judgment for converting.
Any cases where you're dealing with formats on disk or that otherwise go outside your application, like the zip archive format that I discussed, those may need specifically sized types. If you're doing archiving, you may need to look at instances of that to make sure that your archives can be read and written on both 16 and 32 and 64 bit consistently.
Cases where you're dealing with pointers, you need to make sure that all your references to the pointers are consistent. And in one particular case, if you're still using the old non-keyed archiving, you're going to have to deal with both of these issues at once. It could be done. Maybe you want to consider whether you really need to keep using non-keyed archiving anymore.
You need to examine your dependencies. If you're using somebody else's code, then you need to look at that and make sure that it is 64-bit ready as well. And then finally, you need to make sure you thoroughly test the result. And that's what you need to do to make your app ready to run at 32 and 64-bit.
All right, let's go on to our next topic, which is performance and responsiveness. Now, performance is something that you probably shouldn't be thinking a great deal about while you're just getting your app up and running. But once you have it running, it's time to look at it. Now, I'm a great believer in testing performance using user-level scenarios.
If you don't have a specific user-level scenario that shows a performance problem, maybe there isn't a performance problem there. But if you have a user-level scenario that shows a problem, then you need to attack it. And the mechanisms for attacking performance problems are probably pretty familiar to you. Some of you may have gone to the previous session upstairs, the performance technique session that showed some of these things. Basically, the guidelines for dealing with performance issues are very simple, three simple rules. First, don't touch the disk. Second, don't use memory. And third, don't do work.
Now, this may not be quite as simple as it sounds. Let me explain what I mean. Don't touch the disk. Well, the disk is one of the slowest components of a modern computer by many orders of magnitude. So every file system access that you can get out of your program is that much to the good.
This, for example, is one of the main keys of our file system efficiency program for Snow Leopard that we've been talking about, that is to remove any redundant or unnecessary file system accesses. Now, you might not think that your app has any redundant file system accesses, but maybe you should take a look and check and find out. You might be surprised.
Second thing, don't use memory. Well, memory is a lot faster than disk, but still it's a lot slower than the processor. So every bit of memory you can avoid using is that much to the good. Now, of course, it's perfectly okay to use memory if it's a substitute for something that's more expensive, like going to the disk or doing a lot of expensive computation, especially if you are willing to give that memory back to the system when it needs it. And if you went to the performance session earlier, it was discussed, the new caching and discardable contents mechanisms for Snow Leopard that allow you to give memory back to the system when the system is under pressure.
And don't do work. Well, sometimes it's unavoidable, but we've all seen cases where a simple change in algorithm or maybe doing something lazily can get the same task done by doing a lot less work. And then when you come, To the end of these techniques, it's time to start considering concurrency. The way we want you to think about concurrency in your app is not so much as a series of threads, but rather as a series of work queues on which work units arrive, they're scheduled, they're processed, they're handled, they're done.
And this operation queue in Cocoa makes it very easy to think this way. But also, and as run loop is a different sort of work queue on which things arrive, they're scheduled, they're processed, they're handled. Now, in an application, the main thread with the main run loop is special in that it is the work queue on which user interaction events arrive.
And in a simple application, maybe all the work is done on the main run loop. But in a more sophisticated application, you would typically want to move some work off of the main run loop. So as to leave it free to do its main job of handling user interaction, and that's what keeps your app responsive.
So, let's look at the next slide. Now, you've probably heard Bertrand talk about the Grand Central Dispatch push for Snow Leopard. Well, as Cocoa developers, if you're using NSOperationQueue and NSRunLoop, you're in luck. These are the classes that will be the Cocoa interface to the Grand Central Dispatch functionality.
You're already thinking in the right way about handling work queues. Now, one big point from the previous performance session, one point to take away is that you want to avoid as much as possible any concurrent access to shared mutable data. Because whenever you have contention for shared resource, you're going to lose the benefits of concurrency.
It's possible to lock, but as I say, the contention for the lock is going to degrade your performance and your concurrency. Fortunately, there is an alternative. What you can do is create a single-threaded operation queue, and then for a specific shared resource, make sure that the work on that resource is sent -- always sent to that particular operation queue. Then the operation queue will automatically take care of serializing access to that resource for you, and you don't have to worry about locking.
Now, the final thing I want to mention is that in a Cocoa app, there are many potential sources of concurrency, implicit and explicit. You can use NSThread if you need to, but we really want to thank you more in terms of work queues, like at its operation queue, especially for concurrency in your model. In Snow Leopard, we have some new sets of concurrent APIs. If you saw the What's New talk, we discussed the concurrent collection APIs for things like searching and sorting and iterating.
NSDocument has a new concurrent document opening API so that you can make sure if you're opening a number of documents, that can all be done concurrently with respect to each other. NSView has a concurrent view drawing API so that perhaps some of the views in your window can be drawn concurrently with respect to each other. And there are other sources of concurrency.
For example, if you use Core Animation, that's an implicit source of concurrency. The animation is done concurrently with respect to whatever else you're doing. And you can also use the GPU. Concurrently with the CPU with OpenGL, Core Image, Core Video, and now in Snow Leopard with OpenCL. So let's go over to our application and go over to the demo. We'll take a look at it.
And as it happens, I do have a performance testing scenario, user scenario. And my scenario is to successively open larger and larger zip documents and see how the app handles it. So if I start with a very small one, that's pretty quick. If I go to a little bit larger one, that takes a little longer. And then I could go to a really huge one.
Do I dare open this one? Actually, I think I better not try to open it. I tried this before, and really it's not pretty. It just hangs the whole app. Fortunately, I managed to do some sampling. And the sampling will show where the problem lies pretty quickly. In this case, we take a look at the samples, and we can go right down to the document method and see what it's doing. It's calling an NSData method. The first thing this app does is to load the whole contents of the zip archive into memory. Now, it's a really convenient API, but it doesn't scale.
And furthermore, as it happens, we don't really need access to all the contents of the zip archive, at least not at once. We just need little bits and pieces here and there, mostly toward the end of the document, actually, but still. So we can try to solve that and just look at the pieces that we want when we want them. That will remove great many file system accesses. It will greatly reduce the use of memory. Now, is that enough? Maybe.
But we can't necessarily rely on that, because we don't know how many entries are going to be in a zip archive. It could be thousands of them. We don't know how slow our file system is going to be. It might be on a USB drive. It might be over the network. So to be sure, what we're going to do is take our file system access and move it off of the main run loop to maintain. responsiveness in our application. Let's see how that works.
Let me go to the improved version of the application. Now, what I've done here is to add another class, which I called FileBuffer. What this is, is just a very simple wrapper around an NSFile handle that handles access to the contents of the file, and it does a little buffering that's specialized for the uses of this app, just to reduce the number of file system accesses to a minimum.
And, oh, it does one other thing as well. Because the Zip Archive format is a little Indian, I've written the API for this in such a way that this class does all of the byte swapping for me, so there is absolutely no way I can forget about Indian-ness.
And one thing I should mention about this class, a very simple class, it is deliberately not thread safe. Why is that? Because access to a file descriptor, which is what this does, is something that inherently has to be serialized. I could make it thread safe by locking, but I really don't want to lock. Locking is difficult. It's expensive. It's hard to get right. It's difficult to debug. Instead, what I'm going to do is what I mentioned before. I'm going to use an NSOperation queue, a single thread, to serialize the access to this object. Let's see how that works.
I'll go over to my document class. Now, when I set up my document object, I'm going to create an operation queue for this document and make it single-threaded. Okay. And then when I go to read. Oh, notice I'm going to use the new Snow Leopard API for concurrent document opening, just for another extra whammy. Document opening is going to be concurrent with respect to each other.
Now, then when I go to read one of these documents, what I'm going to do is just to create one of these file buffer objects. And then I'm going to go and read just a very little bit out of it, just enough to make sure this thing looks like a zip archive.
And then after that, after that, I'm going to create an NS operation. After that, all the access to this file buffer is going to occur on our operation queue. I'm sure to maintain that. So I created this operation. It's a pretty trivial subclass of NSOperation. All it does is call back into the document, and it calls a particular method.
called read entries for "operation." And this is the method that actually does the work of going through and reading all of the entries out of the zip archive, and it reads everything about the archive, the location, the name, the size, and so forth, and then it creates that model object, the zip entry object for that entry in the archive. And then it does something interesting. What it does is to add that entry object to an array.
And when we get enough entries in the array, then we call back to the main thread, to the main run loop with that array of entries, and we call back to the main run loop so that those entries can be added back into the document and added to the UI, because it's the main thread that does the user interaction. Let's see how well this works.
So now what I'm going to do is open all three of these things at once. Ready, set, go. Boom. All of them opened at once, including this giant one, which contains, I think it's all of GarageBand's data. And if you look very closely, you may have seen a bit of a flash where the UI came up before it was populated. That is the effect of doing this in the background. So that is our performance work on this application. And let us go back to the slides.
So the next thing I want to talk about is security. Now, I can remember a time not so very long ago when, particularly for a simple little app like this, nobody would have thought very much about security. That time is long past. In particular, it is not possible to take a data file from somewhere, who knows where, assume it's a particular data format, read bytes out of it, and just take them for granted, assume they mean what they say. No, you can't do that. The right attitude is external data is poison. It's radioactive. You handle it with rubber gloves, with a 10-foot pole.
So much so that even if you don't have any poison data on hand, you need to create some yourself through a process called fuzzing. And what fuzzing is, is you take a good data file and you modify it by putting in random bits. And you don't modify just any part of it.
You modify the most sensitive parts, the ones that determine the structure of the document. You change the bytes. You insert bytes. You remove bytes. You create a whole set of bad data and see how your app handles them. So I happen to have done that for this particular application. Let's see how it handles it. Let's go back over to the demo.
Okay, here's our original Zip browser. Let's run it. And here's a particular piece of fuzzed data that we've produced. Let's try it. And boom. We get back into the debugger. And here we are at this particular line. And let's look at this directory index. And it looks like directory index is some completely wild value. Well, it turns out the directory index is a value that we happen to get out of the file. And we trusted it. What were we thinking? So what are we going to do about this? What we're going to do about this is introduce some paranoia.
Here's our updated document. And here is the method we were looking at a little before, an updated version of the application. This reads the entries from the zip archive. And then whenever it reads a value, it checks it every possible way. Now remember that integral arithmetic can cause values to wrap around, so you need to check everything not only to make sure to take it above, but also check it below because it might have wrapped around. You check and make sure every value is same before you ever try to use it.
You might worry a bit about performance with adding all these extra checks. Well, there are a couple answers to that. First of all, first answer is that you shouldn't worry too much about your performance if your app wants crashing. The second one... The second one is that these sorts of checks don't really touch the disk anymore. They don't really use any more memory. They just use a little bit of processor time. So they shouldn't make a great deal of difference to your performance.
And in the case of this app, they don't. So let's try this on that same piece of data and see how it works. There's that poison piece of data. Try it. And ah, we recognize that it's bad, and we refuse to open it. So that is security. Let's go back to the slides.
And to our next topic. So the next thing I want to talk about is localization and internationalization. Now, this is something that's very important to Apple because we know that most of our customers are not U.S. English-only customers. And it should be important to you because it could be that most of your customers, if you were interested in internationalization and localization, it could be that most of your customers would not be U.S. English-only, too. Now, in general, internationalization is difficult.
Fortunately, what we have done in Cocoa is we have done almost all the hard work for you. It makes it easy if you just use the classes as they're intended to be international ready. It's not free. In particular, you still need to test your application. You need to test with your localizations. You need to test with various locales, as we'll be discussing. You need to test with content that comes from other places that are in many different languages.
And also, international content can stress different failure modes in your application. So things like that fuzzing testing, you should probably do it over again with international data. There was a very good study that showed that there was a very good study that showed that there was a very good session yesterday on internationalizing your software. You may be able to look at that. I hope some of you intended it. Let me talk about some of these topics specifically.
So first of all, about localization. Now, the things you need to do for localization are things that are good practice generally. You're probably already doing them. Things that you should be doing, even if you don't have any immediate plans, to ship localized versions of your application. These are things like putting most of your UI into your nibs, making the nibs localized resources so that potentially your localizers could produce localized versions of them, making sure that all user-visible strings are localized. So the first thing you need to do is to create a localizable string in a not-strings file that's a localized resource in your bundle.
And you need to be a little careful about how you construct these strings. In particular, you don't want to be making assumptions about what the strings might look like. So you shouldn't, in general, try to programmatically construct things like construct sentences from their parts because you don't know how that would behave in the wide variety of languages. For example, these strings here all mean, I believe, allow editing.
But if you were to try to construct these from parts like allow and editing, maybe you wanted to construct allow editing and deny editing, there is no way that you are going to be able to tell in advance which of these pieces of the string mean allow and which mean editing. So you should just use the whole string and let your localizers handle that for you.
Locale. Now, on Mac OS X we think of locale as something different from localization. Localization is the language that your application uses to communicate with the user. Locale has to do with the way that the user's data is formatted. These are things like date formats, number formats, calendars, sort and order and so forth.
And the biggest thing to remember when you're using these is to make use of the appropriate system classes, things like NSDate formatter, NSNumber formatter. These will automatically handle the proper formatting. You should create them. Don't try to use -- as much as possible try to avoid hardcoded format strings.
And just let these classes do their thing. They know how the user wants these pieces of data presented. They usually have various different options that you can turn on and off. For different kinds of formats. Shorter formats, longer formats and so forth. And don't make assumptions about how they're going to end up formatting the dates or the numbers.
Here are some various examples. Along the top we have various number formats. Some of them using the Indo-Arabic digits. Some of them using the Arabic digits. We have some date formats. In general you're not going to be able to predict what these will look like across languages. So don't try to do it. good.
Text handling. A great deal of international data is international data in many different languages and many different writing systems. To handle that, I shouldn't need to mention you use Unicode. In particular, in Cocoa, you use the classes NSString and its allies, NSAttributedString and so forth. If you want to display these things, you can If you want to have your users edit text, please, I recommend using the Cocoa text system for this. We have done a lot of hard work to make sure that international text can be displayed and edited properly.
You should not just use NSString and NSAttributedString classes, but you should make sure you use their APIs for things like searching, sorting, casing, substrings, and so forth. You don't want to be in a position where you are trying to go through a string character by character. For Unicode, that doesn't even work generally, because in Unicode, A particular visible character may be made up of many characters in the string.
There might be, for example, a base letter and its accents as separate characters. In Korean, you might have a hangul jamul separately that combine to form a single user visible we call graphene. So if you have to deal with strings, in Cocoa these are referred to as composed character sequences.
If you're going to take a substring of a string, make sure it lies on composed character sequence boundaries. And then a string has APIs for these. And finally, don't make assumptions about how the text is going to display. Some text displays left to right. Some text displays right to left.
Here's an example I've given before. So at the top, we have the characters as they appear in the string, some English text, some Arabic text. At the bottom, we have the glyphs as they're displayed by the text system. There isn't a one-to-one mapping between the characters and the displayed elements. And it doesn't go uniformly from left to right. It starts out going from left to right, then it turns around and goes back from right to left. So you can't necessarily assume how strings are going to be displayed.
Something new we have in Snow Leopard, talking about composed character sequences, is a block-based API for enumerating through a string. And you can go through the string many different ways. You can do it by composed character sequences, by words, by sentences, by lines, by paragraphs, just by passing different options to this API. And we have an even simpler convenience API that just goes through a string by lines if you want to go through the lines in a piece of text. Very simple, easy to use, based on blocks.
One last thing, encodings. And a string is what you use for text inside your application. If you want to write something out in persistent format, if you need to deal with anything that goes outside your application, then you're going to need some encoding form for the text. And of course, we strongly recommend that you use one of the Unicode encodings, UTF-8 or UTF-16 particularly.
And a string will easily do a conversion between any encoding that you may want and the internal form in a string. We have a specific API, the file system representation APIs for strings that are considered as names in the file system. UTF-8 is used for most other purposes. But you're probably going to occasionally encounter legacy formats with other encodings. Again, you can use NSString to do the conversion.
Now, sooner or later, you'll probably run into a case where you have some bytes that you think are text, but you don't know what encoding they're in. But what do you do then? You have to guess. You may be able to guess something based on the context in which the text appears. You might be able to guess something based on its contents, maybe not.
We recommend a good thing to do is try UTF-8 first because it's a very specific encoding, very difficult to mistake anything else for it, very stringent. Then you might try something else that you might have guessed based on the context in which it appears. And you might try some other fallbacks. We recommend that as a very final fallback, you use Mac Roman because that's always going to succeed with any random string of bytes.
And, but you have to understand that if you're going to guess the encoding, sometimes you might get it wrong. So one final thing that you can do is to allow the user to change the encoding, to take what you have guessed and change it to something else and reinterpret And I have an example of that. Let's go on to the demo. We'll take a look at it.
So let's go back to Xcode and take a look at our original example. And down at the bottom here, I have a couple of international zip archives. Now, I want to say something about the zip archive format. In spite of the fact that it is included by reference in a couple of -- several different ISO standards, the zip format is not itself standardized. What we have is a set of notes from the people who implemented it originally.
And up until very recently, these notes didn't really say anything about the encoding of the names inside the zip archive files. Originally, actually, they used -- the first implementation used a DOS encoding, CodePage 437. But I think in practice what most people have done is just take their file system names, byte for byte, copy them into the files without worrying about encodings. So you might encounter just about anything in there. Mac OS X uses UTF-8. And more and more things are using UTF-8 now. Actually, the format now has a little bit for specifying UTF-8. I don't know that anyone uses it yet, but it's there.
So this leaves us with something of a conundrum. We have to do something about it. We have these bytes in the document format that are names, and we don't necessarily know what encoding they might be in. So let's see what our original application does with one of these.
And it fails to open it. Let's see what it does with this one. And it fails to open it. And the reason it does this is because when we originally wrote this first version of the application, it just assumes that the contents of these names are going to be UTF-8. That's what Mac OS X does. That's what it assumes. If it can't, it fails.
Let's take a look at how we can improve that. And let's go to the updated version. And try to open these same documents. Let's start with this one. And this one opens correctly. I see I have a name with a couple of accented Es in it. And the reason it opens correctly is because we have some fallbacks here. If UTF-8 doesn't work, we try some other encodings.
Let's try this other one. This one opens, but it doesn't look quite right. I don't think that's really the name that was intended. So in addition, We've used our fallbacks, and they produce something, but it doesn't seem to be right. So we've added a Reopen with Encoding menu to this application, so we can try a different encoding. UTF-8 is the default. Let's try Windows. No, that doesn't seem right. Try that old DOS encoding. That still doesn't look quite right.
Maybe Mac? No. I think maybe it's a Japanese file, so let's try some Japanese encodings. That one doesn't look right. Hey, that one looks pretty good. OK, so we've given the user the ability to correct our guess if the guess was wrong. Let's see how that works, the code.
So we're back to the method that reads the entries out of the zip archive. And here's where we're interpreting the name. So we start off, each document has an encoding. The default encoding is UTF-8. We start off by interpreting it, trying to interpret the data without encoding. If that doesn't work, we use the file system representation, which is a form of UTF-8. If that still doesn't work, we'll try the standard Windows Latin. and Cody. And as a final ultimate fallback, we fall back to Mac Roman, as we know that always succeeds.
And how about the reopening? We've added some support for an action method, reopen with encoding. And what this method does is, first of all, remember that all the file operations for this document take place on our operation queue. So if the document is still being -- in the process of being read, we have to stop that and wait until it's done. And then we're going to try to re-read it using a different encoding. And the way I chose to do this was to make the encoding as the tag on the menu item.
And so we set the document's encoding to that specified encoding, and then we reopen the document using the standard read method, which goes back through and then reads the document again using the new encoding. And that is how we enable support for these different encodings. And then we're going to go ahead and run the process of re-reading the document. And then, You remember that we had some numbers in there.
And you'll remember the way we were presenting those numbers, we were using present-half style formatting with percent, and ultimately These are numbers that are going to be presented to the user. So we want to make sure they're properly formatted for that. So for that, we use an NSNumber formatter.
So I've created code that creates an NSNumber format or sets an appropriate style, decimal And I decided to not use the grouping separator. The grouping separator is like a comma in English usage. Some other places, a period is a thousand separator. And then for each of these numbers, we're just calling string from number to get an appropriate string from that formatter. And then in our methods that produce these strings that we're going to display, first of all, we make sure they're localized.
And this localized string makes sure that these format strings come from a localized string. So we've got a localizable.strings file. And then we use present at as a format. And in it, we get the string value that we got from a number formatter. That makes sure that the numbers are properly formatted for international use. So let's go back to the slides.
And on to our next topic. The next topic is usability. Now, this is a very broad topic, and everybody has their own opinions about it. But in general, what happens is that most applications are going to be originally developed and used by a small group of people who know the app really well.
And in order to understand more about their usability, you need to see how they are used by larger groups of people who may work in many different ways, people who don't necessarily know the app really well. So you should try your app out with groups of people and see what they do, see what they understand, see what they don't understand. Pay particular attention to the defining features of the Mac interface, simplicity of interface with direct manipulation at the heart of it.
And when you find that you can do something automatically for your users, you may provide a way for them to opt out. It's a good idea to do it. We spent a lot of effort, for example, making sure that you can do things like saving state so when your users come back into your application, it's easier for them to get back where they were. Now, there are a lot of things that I could show for this, but I picked one. And let's go over to the demo machine, and I'll take a look at the one thing that I decided to show here.
Let's go to our original application, launch it again, run it, open a zip archive. Now, One thing I know is that Mac users being used to direct manipulation, when they see something like this, they're going to want to try and pick up this thing and drag it. You know, it doesn't drag. I can't drag this thing out to the desktop. I didn't add support for drag out of my application. So let's do it.
Let's go into Xcode. Let's take a look at the updated version of the application. And let's see what we needed to do in order to add drag and drop support. Let's go into the document class. And now there are several different code paths for this, depending on exactly where you're dragging from. But I'm going to show you how you add dragging support using the browser with the new item-based API. And that's very simple. We have a few methods.
One of them is canDrag. And I decided to implement dragging of just a single item, if a single item is selected. And I implemented a common method that returns the entry that is selected if dragging is allowed for that particular selection. And then you get to provide an image for the drag. And I chose to use that icon from NS Workspace. It's the same thing which we're showing in the view. And so I get the entry, and I get the icon from NS Workspace. And I return it.
Then, when eventually we're going to be called to actually write these things out to the pasteboard for dragging. And for dragging to the finder, I'm using the file promise pasteboard type. I'm going to be really lazy about doing my pasteboard operations. With the file promise pasteboard type, all you do at the original drag time is you declare types, including the file's promise types. And for the file promise types, you add an array. And the only contents of the array are the type of the file. You don't have to write the file out until it's actually dropped.
And then when it's actually dropped, We got another browser-delicate method that-- sorry. that calls on us to redeem our promise. It's called names of promised files dropped at destination, which means we have to write the file out in the destination folder. And I implemented a method that does that. Write drag rows with indexes.
That does the writing of the file, the entry in the archive. Actually, it doesn't do it. Remember that all the file access in this class has been sent to our operation queue. So what this method does is create an operation and put it on the queue. We create a write operation. And again, this is a trivial subclass of NS4 operation. All it does is call back into the document as a particular method. Let me show you that method.
There's the write entry method. And what this will do, it'll be called on the operation queue, and its job is to take that entry in the zip archive. It may be compressed, and if it's compressed, it'll have to expand it, and then write it out to our destination URL.
So remember, again, We take a look at every value that we get out of the file, and we treat it with the utmost suspicion. We have to check it above and below and make sure it makes sense before we do anything with it. If we think it makes sense, Then we are going to take a look and get the actual data out of the archive.
As I said, it might be compressed, it might not be. If it's not compressed, we simply write it out. If it is compressed, we're using the libz here to do the uncompression. And it has a fairly straightforward API. In this case, we're uncompressing it out into another data, immutable data.
And we were sure to check all the return values to make sure they make sense. There's actually a CRC value. So we check the CRC in each case. To make sure that it agrees with the values of the store. And if it's not, we don't trust that data. And then we write it out to our file.
And let's see how that works. Launch and run. Open up the zip archive again. Take a look. Try to drag it out. It's going. Now we're on the desktop. Let it go. There it is. Let's make sure that file's really there. Open it up. There it is. So our drag and drop works. Let's go back to the slides. And the next topic I want to deal with is scripting and accessibility. But in order to do that, I've asked our accessibility guru, James Dempsey, to come up and talk about that for us. So welcome, James.
Thank you very much, Doug. So usually when we think about users interacting with our applications, we think about them with an input device and mouse events, key events, multi-touch gesture events even. But on Mac OS X, there are two additional ways that users can interact with your applications, scripting and accessibility. And in fact, it's two ways that users can interact with your application from a separate process. And so here we have your application. And let's say the user starts running a script. And using inter-process communications, they're driving your application.
And from accessibility, it's the same way. We call an application or process using accessibility an assistive application. And through IPC, it can also drive your application. Let's take a little bit closer look at your application. We'll go down to a kind of well-factored Cocoa app with model view and controller.
So scripting, the IPC method that it uses is Apple Events. And your application provides a scripting dictionary, which is essentially a set of classes and commands that are a scripting representation of the model in your application. And so when a user is scripting your application, they're interacting with your app, driving it through the model. Doug Davidson, James Dempsey With accessibility and assistive application, the IPC mechanism that it's using, Our Mac OS X's accessibility client APIs, the AX APIs. And an assistive application is getting information about and driving your application through the view.
So they both drive your app from another process, but they do so in very different ways. And they do that because scripting and accessibility, while similar in some respects, have very different purposes on Mac OS X. Let's talk about scriptability first. It's the foundation for building automated workflows on Mac OS X.
And since workflows, taking some data from one application, moving it along from app to app, processing it along the way, it's a very data-centric activity. It makes a lot of sense that scripting deals with the model of your application. It's not about selecting this menu or it's about grabbing the people out of your address book, doing something with them in this word processor, doing something else with them in a database.
Scriptability, as you might imagine, gives users a great deal of flexibility to work your application into all kinds of workflows, things you might not have even imagined your application would be used for. Now, back at the turn of the century, scriptability essentially meant users using AppleScript. But since the turn of the century, we have a lot of additions to this.
When you make your application scriptable, that also forms the basis for you to provide automator actions so that users of automator can build kind of higher-level automated workflows. And with the Apple Event Bridge, not only AppleScript as a language, but use Ruby or Python or even a compiled language like Objective-C.
Now, we don't have a great deal of time in this session to get into making your app scriptable. But fortunately-- oh, I forgot one small point, which is scriptability is a feature you implement. Your application opts in to scriptability. And we do have a session just happened, but it'll be coming out on your IDC on iTunes subscription, Making Your Application Scriptable. I highly recommend taking a look at that session and doing just what its title says.
So if scriptability and scripting deals with your model, and it kind of makes sense why, why does anybody need access to my view anyway? So what I'd like you to do is just consider for a moment the case of a user with a visual disability. Trying to use a graphical user interface.
The user is unable to see what is on the screen. And so that user needs to have what's going on in the interface described. They need to be able to navigate and interact with applications without using the mouse. And they also need to get notified when things change, like when windows and sheets come and go.
And so the primary purpose of the Accessibility API is to provide users with this alternate interface. Now Mac OS X ships a built-in screen reader application called VoiceOver. It's been shipping since Tiger. But there are also many other third-party applications that use the Accessibility API to provide this alternate interface.
Now the way it works is it's a collaboration between two processes, your application and the assistive application. So your application provides accessibility information about what's going on in the UI, and then an assistive app like VoiceOver takes that information and presents a unified, specialized interface for users. Now, this is a public API, and in addition to its primary purpose, there are other uses of that accessibility API.
So if you've seen demos in Instruments or Automator, there's a record and playback of the interface feature. That is using accessibility. GUI scripting is using accessibility, which is a handy way to build automated user interface test suites for your application. And then numbers of third-party apps and utilities also use this API.
Now, not everybody realizes it, even Cocoa developers that may have been around the block a few times, that it's not opt-in. Your application, if it's out there in the world, it's already reporting accessibility information. VoiceOver users have already downloaded it and taken a look. And if you've done no accessibility work on your app, then the results will be somewhat good, bad, or ugly. But if you've done no accessibility work at all, there'll be your share of ugly in there. So let's talk about how we would address that.
Actually, before we do that, one more point about application accessibility, which is, what is this information that my app's already given out? I didn't even know it was. What's going on? It's a lightweight representation of your user interface. So that accessibility client gets a small thing, a UI element ref, which is essentially a small token or a small proxy back to a real user interface element in your application.
Each of these little UI elements that the accessibility client has has a particular role that lets you know what the type of thing is. Is it the application itself, a window, a button? And this forms a hierarchy of user interface elements that describe your UI. Now what does that map back to in your application? It maps back to actual NSObjects and NSObjects subclasses.
That accessibility information is provided by objects that implement the NSAccessibility protocol. Now, all the heavy hitters in AppKit already implement this application, NSWindow, NSCell, NSView, NSControl. And so in AppKit, there's already a great deal of accessibility built in. So you're already getting a good default amount of accessibility.
However, there's always more to be done. There's always a little more you can provide. So some examples are, if there are images in your user interface, it's easy for us to describe a button to somebody if it has a text title. But if it's a picture of something, we don't know what that something is. We need you to go into Interface Builder, select that button, type in a title in the accessibility inspector portion of the inspector.
Connecting related UI elements like what text fields go with what static text. Full keyboard navigation. A user who has a visual disability isn't able to use the mouse. They need to use the keyboard to get everything done. And then finally, if you've created a custom view or a custom control, the default work we can do in the kit only goes so far. We don't know what you're doing in that control or that view.
So you need to provide the accessibility information for us. And the tools you can use to test that accessibility, accessibility inspector, accessibility verifier, or turning on voiceover and navigating around to see how your app appears. in VoiceOver. Now, we're just scratching the surface here. I would very much encourage you to take a look at the application accessibility session from earlier this week. But for now, I'd like to show a little demo.
[Transcript missing]
And in the doc is Accessibility Inspector. And as I point to various things on the screen, it uses the Accessibility API to give me information about those elements. And so if I take a look at the window, I'm getting a lot of information about its size, its position, et cetera.
If I look within the view, all of the standard controls I'm seeing, okay, these are all nicely accessible. If I go to our custom view, however, you'll notice it doesn't believe anything is there at all. And so what I'm going to do is let's take a look at how we'd fix that in code. Let me close the inspector here.
Now if we just peek very quickly at what that custom view is, Let's see. Go to draw rect. And you'll notice here that it's a fairly simple draw rect. However, I mentioned that what we would like -- or I didn't mention -- what we would like to happen is for that image to show up as an image, each of those static elements in the inspector to show up as static text.
Yet we don't have an object on the back end to implement the NS Accessibility Protocol. So we have two options. We could either create some NSObject subclasses whose only job in life is to implement the Accessibility Protocol and report the correct information for those classes, or we could take another approach, which is let's take our custom view and replace it with standard Cocoa views. Because this custom view isn't doing a great deal.
It's just a view, and then from that view it's drawing an image and a few pieces of text. If we were to replace that with a view that just had an image view and some text fields in it, we would pick up the accessibility for free. So sometimes the best way to implement accessibility is to just use kit classes. To that end, let's go with that approach.
[Transcript missing]
Set our inspector up. And you'll notice now we are getting an AX image, we are getting a static text, and so we are reporting accessibility information. And that's all I have. Back to slides, please.
So here are some take-home points. Once you've got your application up and running, there's usually a lot more to be done. Important thing is to test your application with a wide variety of scenarios and data, and consider these various points: 64-bit, performance, responsiveness, security, localization, internationalization, usability, scripting, and accessibility.
For more information, you can contact Derek Horne. We have documentation and sample code. This should be up soon as sample code. There are a number of other related sessions that we've been discussing. Most of them have already taken place. There's a session after this on performance and document-centric Cocoa Apps. Now, we were going to do some Q&A, but since James is here, I thought maybe we'd get a song instead. So, let's welcome up James Dempsey and the Breakpoints.
Great. So for those of you, from the sound of it, somebody's seen something like this before at WWDC. And you may notice that there are a few things missing. One, no guitars and no lead guitarist. So this year, Gordy Friedman, who is our lead guitarist for the Breakpoints, about a week, week and a half ago, found out he was not going to be able to make it to WWDC. So we, through the magic of GarageBand, he's going to be with us in both sound and in spirit. We figured since we were mixing things up, maybe this year I'd get out from behind the guitar as well and just try to belt one out for you.
So, let's see. Oh, so I just wanted to get a show of hands real quick. How many folks here are pretty new to Objective-C, like in the last six months to a year? So, a few folks. Great. How many folks have been doing Objective-C for at least a year? Awesome. Three years.
Five years. How many folks are named Oli Ozer? Excellent. All seven of you. So I think it's extremely exciting at WWDC this year because with Mac sales increasing all the time, it seems, with the new iPhone SDK, I think this is perhaps the time in Objective-C's history where the most new people have been coming into the language, which I think is fantastic. What I wanted to do this year was a little something that maybe will give a little advice to some newcomers, but also do something that the old-timers, I hope, will really appreciate. So without further ado, let's hit it.
♪ Designated initializer ♪ ♪ It got the better of me ♪ ♪ Yeah, yeah ♪ Designated Initializer Oh, it got the better of me ♪ It's a cautionary tale, my friend ♪ ♪ I'm sure you will agree ♪ ♪ Well, don't do what I did do ♪ ♪ Or it'll happen to you ♪ ♪ Like it happened to me ♪ Let's start at the beginning.
♪ Well when you unlock a memory block ♪ ♪ It's allocated with precision ♪ ♪ That is a pointer's assigned ♪ ♪ So your instance can find its class's definition ♪ ♪ All the other i-vars are filled up with zeros ♪ ♪ Thus nil-dip to objects they refer ♪ ♪ And then your instance must wait ♪ ♪ To be put in a good state ♪ ♪ By an object initializer ♪
[Transcript missing]
♪ You got the better of me ♪ ♪ No I didn't forget ♪ ♪ But I did forget ♪ ♪ We'll get there soon, just wait and see ♪ ♪ A class with many a nits ♪ ♪ Use the one that best fits ♪ ♪ There's power in the variety ♪ ♪ I could be subclassed right ♪ ♪ Or be debugging all night ♪ ♪ Like what happened to me ♪
[Transcript missing]
as a funnel point. Its setup logic makes it much wiser. That funnel point, well, it passes to its superclass's designated favorite son. Though it may sound like a blooper, the rest don't call super. They call your chosen one. Now here's what happened to me.
I wrote a subclass. I initialized a set of critical states. Critical. I didn't override my super's init methods, which was ill-advised. Ill-advised. Anyone calling those init methods doomed the new instance to a crashing face. My superclass's code was called, but never mind. My critical state was not initialized. I initialized that designated initializer. It got the better of me. It was a subtle catch with dynamic dispatch. Yeah, it got the better of me.