Development Tools • 1:04:26
Xcode has many advanced features designed to make your development more productive. This session will help current Xcode users get the most out of Xcode. You'll learn the best ways to structure your projects, how to get maximum build performance, and much more.
Speakers: Bill Bumgarner, Chris Friesen
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Welcome to Power Programming in Xcode. This session we're going to look at both some of the features that have been in Xcode for a while as well as some of the new refinements we've done for the 2.1 release and really focus on using the tool to make your code, make it work, and keep it working.
So, I'm Bill Bumgarner. I will be joined shortly by David Ewing. We're both in developer tools architecture. And so first, before we really dive into this, let's look at software development. You know, what's really the issue here? Bottom line is software development just takes too dang long, and it's repetitive. It's a lot of making mistakes and a lot of finding and fixing those mistakes. You know, and really, time flies when you're mired in tedium. I think we've all experienced this.
So... Looking at specifically where the time goes, it's all about that edit, compile, run, debug loop. Or now the model, edit, compile, run, debug loop. And a part of that is writing your code, which is, you know, it's advanced word processing, more or less. And, you know, you want to, it's just a lot of complexity, a lot of details to remember there. And then you move into, you've got to build the products.
Now, with the applications we're building today, you are very often targeting multiple versions of an operating system. Now you're targeting multiple kinds of CPUs. As well, you're looking at having different configurations. You've got your debug configuration and release configuration, maybe a testing configuration, maybe even like professional versus light configurations.
You also need to figure out whether or not it works. And that can be actually challenging. It's not just the quality assurance of double-clicking the app on the desktop, making sure it launches and runs and does what it's supposed to, but it's also verifying that the underlying functionality is working, your algorithms are correct, and things like that. And once you've identified that there are problems, you've got to spend the time to fix what went wrong. And this is the debugging process.
And as well, you got to make sure you didn't break other things. Because as you know with software, software is a very complex beast. And if you change one thing over here, it may break something over there even though the two seem unrelated. And then you get back to the repetition part. So really, software development is really just a highly repetitive process. There's lots of tedious steps. And lots of research shows that tedium plus repetition means lots of silly, often time-consuming mistakes.
So how are we going to use Xcode to develop this process, or what features have we really added to Xcode with the goal of making this process less tedious and more productive? First, we want to reinforce the big picture. Software is more complex now than it's ever been before. In particular, your frameworks just simply have more functionality and are doing more for you. So even if you're writing less code because you're using things like Quartz Composer, Core Data, and Spotlight and these other things, there's more moving parts underneath and sometimes they can mash together.
First, we want to reinforce the big picture. Software is more complex now than it's ever been before. In particular, your frameworks just simply have more functionality and are doing more for you. So even if you're writing less code because you're using things like Quartz Composer, Core Data, and Spotlight and these other things, there's more moving parts underneath and sometimes they can mash together. And whenever you do make a mistake, the mistake should be indicated very clearly when Xcode can figure out that there is a mistake present. And it should really remember your past successes so you don't reinvent the wheel in the future.
[Transcript missing]
You need to understand how your code works. Everybody around here is dealing with huge projects most of the time, and you need to have the navigation capabilities that let you move around through your code. So then we're going to talk a little bit about creating new source files and some of the extensibility features in Xcode that let you do that, customize the way you do that. And we'll talk about writing code itself accurately and actually some new features in Xcode 2.0 that help you do that more accurately and extend what's there.
So, understanding the system APIs, hopefully all of you, if you're using Xcode now, you use the integrated documentation viewer that's there. The features, it supports nice text searching, API searching. One of the best ways to get to that information is using the option double-click functionality, where you option double-click on a word in your source file, and it starts an API search in the documentation viewer. The new in Xcode 2.0 is automatically updating that documentation over the internet. And of course, there's lots of new content. Here's an example.
This is the Xcode 2.1 user's guide. I mean, this thing is huge. Xcode 1 was, I don't know, probably a quarter of that thickness. So another new thing in the documentation viewer itself is the man page support that's in there. So the APIs, system APIs are indexed. And in fact, the whole man page system is indexed for full text searches. And of course, there's lots of new documentation in Tiger.
So let's talk about moving around in your code. There's lots of ways to do that. There's the class browser that lets you get access to the system classes and your own classes. There's lots of options for navigating through that. You can click on items in the list and go to declarations or option click to take you to the definitions.
There's the Project Symbol Smart Group, which lets you focus in on the source in your project itself. And it's not limited just to classes. There's of course functions and type defs and preprocessor macros are listed there. You can use the search field to filter down to what you want to look at and go directly to that code.
And the best way, at least I find, to move around through your source code is command double click. Click on a word and it does an API search for you right there. And if there's multiple choices, it'll bring up a little pop up you can select. If there's lots and lots of choices, it'll actually do a definition search and bring up the batch find window.
And what's new in Xcode 2.0 with class modeling, you can use that to navigate through your source code too and go directly to the source from the class model. You can create these class models by just clicking on an item in the groups and files group and say click quick model in the design edit in the design menu.
Okay, let's talk, now that you've sort of found your way around through things, let's talk about creating code. So we provide lots of file templates on the system for creating new types of source files. You can--well, there's lots, you know, for Java, Cocoa, Carbon. But you can create your own file templates too, and I strongly suggest you do.
It really makes your life easier. Those of us who live, you know, in a corporate environment where you need to have special headers at the top of the file that has the copyright information, by creating your own file templates, you can do that very easily. So you can look at what's there installed on the system sitting inside /library.
And when you create your own, the best place to put them is inside your home directory in the library subdirectory. And one of the cool things, of course, about creating file templates is that they do macro expansion. You can have words in the file that get expanded when the file is created.
One of the common ones, of course, is the ever-present organization name that occurs in most of our file templates that we ship. And that gets expanded by default to __mycompanyname__. And that's not particularly useful, but you can use the user defaults. You can--at the command line, you can set the macro--the default macro expansion for that. That's actually documented in the expert user defaults item underneath the helpdesk. the help menu in Xcode. So that's a good place to look for stuff.
Okay, let's talk about actually writing code. So we have a number of features in Xcode that we, together we call CodeSense that help you accurately enter your new code. Something that's familiar from the beginnings of Xcode is the completion command itself. That's available in the edit menu. And that brings up a pop-up list that contextually helps you insert code.
You can scroll through the list and see what you're looking for. You can type to filter it down and the list shrinks down. That's been there for a while. Something that's new in Xcode 2.0 is the next completion command. And this immediately inserts what Xcode thinks is its best guess into your text.
Xcode itself uses lots of heuristics to come up with a short list of what it thinks are its best guesses. And the next completion command actually will cycle through that list of commands. So if the first guess isn't what you were looking for, you can hit the command again. And it'll cycle through its best list. And if it's not one of the top items, what you're looking for, of course, you can bring up the completion list right where you are.
And you can move back and forth between the completion list and the next completion command, too. One of the things when you actually insert the text into a file is that it will put argument placeholders in places where you'll need to fit in. Where you'll need to insert some code.
There's a select next placeholder command also in the edit menu that you use to move between those. You can also double click. You'll notice that we'll see this in a little bit. But those placeholders have angle brackets and pound signs quoting them. And you can double click on those and have it select.
But one other feature that's new to CodeSense is text macros. So these are a way to extend not just the completion commands, but you can get to the text macros through the menu system as well. And you can--well, let's talk about that here. Text macros. So they're in the edit menu.
Now, when you're typing code, the most convenient thing to do when you insert a text macro is not really to go up into a menu and down into a hierarchy. But the one thing that's cool about having these in the edit menu is that they can then have key equivalents assigned to them. And you can go into the Xcode preference and go to the key bindings panel in there and assign key bindings to these macros.
So you really can keep your hand on the keyboard and insert text macros. But of course, you can get to them right through code completion. The completion list and the next completion commands also know about text macros. We'll show a little bit about how that works and how that hooks together.
And of course, you can add your own. What good are text macros if you can't add your own? So creating new text macros. Text macros are defined in property list files, standard property list files on Mac OS X. The files themselves need to have the XCTXT macro extension so that we know what they are.
We ship with a bunch that sit inside the Xcode application itself inside the plugins folder. And that text macros.XCTXT macro file there is actually a bundle and the macros themselves are sitting further down inside there. But it's really easy to find that stuff and look at them. When you define your own text macros, you should put them in your home directory inside of the specification subfolder where all of our, where a number, well there's, there's a bunch of file templates go inside there too, right, inside application support, Apple developer tools.
So what can text macros do? Well, if you put your text macros in a menu, you can actually set them up to cycle through a list of macros. So let's say, for example, you want to insert a for loop. Well, there's probably lots of different types of for loops that you want to insert. Oh, I want to say for i equals 0 to 10 or whatever. You might also want to do a for loop that goes through an NSArray.
So you can define macros to do all those sorts of things. We actually have a bunch of macros for for loops and some similar things you can add to that. But you can set the menu up so that when you hit the key to insert that macro, it inserts the first choice. If that's not the one you're looking for, it can cycle to the next choice and so on.
So macros can also inherit settings from other macros. So we'll see in a second that macros are really just dictionaries that can, and they can inherit their settings from their parent macro. And they can inherit settings from user defaults too. So you can set your macro to insert some text that actually gets set using a user default. One of the common things is some of our macros, for instance, for the for loop, are actually By default, it'll put that curly brace on the same line as the for statement itself.
Well, the way that's done is that there's a setting in there called block separator that sits between that closing paren and that opening curly brace that we, by default, define to be a space. Well, you can define it to be a carriage return, so using your user default, so that when you insert the text macros, it puts the curly brace on the next line.
Okay, let's show one here. I defined a little text macro called NSEnum and used code completion to insert that into the text. And it expanded to this multi-line thing that had placeholders in there and you can move between the placeholders. So very convenient. I type five characters, hit complete, and boom, all that stuff went into the text file. So what does that macro actually look like? Well, there's an identifier.
It's got a name. It needs that name because that's how it gets hooked into the menu system. And it has a parent macro. By saying it's based on objc, that means it actually will go into the Objective-C menu that's up there, so it makes it easy to find. It will show up in the menu item with the name enumerator loop. It also, at the bottom there, it says completion prefix. Well, that's what you type if you want to hook into the code completion system. All right.
The text string itself is mostly obvious. I mean, it's just the code that gets inserted, and you can put placeholders in there that hook into the code completion system for moving between placeholders. The one cool thing there, the placeholder you'll notice that's highlighted is surrounded by angle bracket pound sign exclamation point.
Now, what that does is that bit will get replaced with whatever the current selection is. So if you select some, let's say a couple of lines of code, and then use this macro from the edit menu, it will end up wrapping that code with this macro. So you can put stuff inside of a loop easily by doing this.
Well, let's talk a little bit about source control management and project configuration, what's new in Xcode 2.0 and 2.1. So source control management. Source control management is about leaving, not leaving any of your changes behind. You need to know the history of what changes you've had. So what's new? Well, in 2.0 and 2.1, we've improved the Perforce support greatly. So those of you who are using Perforce, yes. A little applause there, that's right. I think you'll be much happier with what's there now. We absolutely want to know if we've missed a few things. But boy, it's so much better than it was.
We've added initial support for unwrapped files, such as nib files. So this, of course, matters for the Perforce folks and the Subversion folks. But it also matters for those using CVS. The mainline CVS that's out in the wild doesn't support wrapping nib files and checking those in and out. You really need to use unwrapped files. So the subversion support itself has been greatly enhanced. And a lot of this is also due to the fact that Subversion itself has matured quite a bit with version 1.2.
[Transcript missing]
And a lot of this is also due to the fact that Subversion itself has matured quite a bit with version 1.2. There are things you can do to enhance the precompiled header support that's built in, and there's some new things in Xcode 2.1 that allow this to be even better than it was before.
There's predictive compilation. So this is something you turn on in the build preferences pane. It's just a checkbox there. And what that does is that when you save your file, it'll actually go out and start a compile of that file rather than waiting for you to go do a build. Now it won't show any errors or anything like that right there, but it'll speed up the process if you decide, oh, I really am ready to do a build or do a compile. Most of the work has already been done by the time you get there.
There's predictive compilation. So this is something you turn on in the build preferences pane. It's just a checkbox there. And what that does is that when you save your file, it'll actually go out and start a compile of that file rather than waiting for you to go do a build. Now it won't show any errors or anything like that right there, but it'll speed up the process if you decide, oh, I really am ready to do a build or do a compile.
Most of the work has already been done by the time you get there. Universal builds don't live together right now. So in your debug build configurations, you want to set just the current machine architecture as the one you want to build. And then you can still use ZeroLink. ZeroLink is supported on the Intel.
Then there's distributed builds. Of course, it makes no sense to have all these computers sitting around your office that are just sitting idle. You want to help speed up your build by using those. Then there's distributed builds. Of course, it makes no sense to have all these computers sitting around your office that are just sitting idle. You want to help speed up your build by using those. And there's Automator. Automator has some really cool things, a new feature there in Tiger.
[Transcript missing]
But you can go a step further. With Xcode 2.1, we can share these precompiled headers across projects a lot more easily than you could in previous versions. So in order to do that, there's two main things that you have to do. You have to make sure that your prefix file actually has the same name in each case where you're using it.
And the contents need to be identical. You can't have separate comments, oh, this is my prefix file for this project, and it still includes the same source. We actually look at that contents to make sure they're identical. The build settings, of course, need to be the same between targets that build and share precompiled headers. We're actually a lot better about determining which build settings matter, though. In older versions, we weren't so good. In older versions, we weren't so good.
Distributed builds, sharing the load. So this is really easy to set up. You just go into the distributed builds pane of the preferences panel. And you turn on sharing for your machine or not. But what you need to do is go to the other machines that you want to use to build your project with and make sure that they're set up to share. The machines need to be running the same version of the OS. And they also need to be running the same version of the Xcode tools, including the GCC compiler.
But once that's done, you can start using the sharing. We've actually fixed a lot of bugs in this, too, in the 2.0 release and 2.1 release. Where it would have a hard time knowing whether a machine was offline and things would-- It would sort of forget about a machine or a machine would be, it would hang a build.
You can--no, so same architecture also, right? The machines need to be the same architecture, so you cannot share between a PowerPC and an Intel machine. So they really are much faster. You take one example project here, relatively small, not terribly small project, went down by a factor of two and a half by distributing to three other machines.
So scripting Xcode. Xcode build is the main thing for scripting builds. We at Apple internally use Xcode build to build hundreds and hundreds of projects for our builds. It's very easy to use from shell scripts. It has command line options to specify which build configuration or which target you want to build. It's great for doing your release builds.
AppleScript support is much, much better. 2.0 improved things a lot. 2.1 went a bunch of steps further. So you can now create and configure your targets and set build settings via AppleScript. You can get to shell script build phases. You can add files to your project and add them to targets. You can run builds from AppleScript. So of course, if you can run builds, you can run builds of multiple projects, right? One thing we don't support yet is creating a project from scratch using AppleScript.
Automator. Automator is new in Tiger, and it has some built-in actions that will do builds. It also has actions that support some of the CVS commands, and it does versioning with the AGV tool that we ship with. With that, I'm gonna bring Bill back up, and we're gonna talk a little bit about debugging.
Thank you, Dave. So... Now that we have all these awesome tools for writing lots of code lots faster, and you're working with a higher level framework, so that means you get to write bugs faster, and they're more abstract. So we figured we really needed to bring the debugging tools forward.
What is debugging? Well, debugging is all about figuring out whether or not something works right and also fixing what doesn't work. Now, what's interesting about this is especially when you start looking at complex applications that are using the higher level APIs, setting a breakpoint on a method doesn't do you a heck of a lot of good if it's the 167,382nd execution of it that breaks. That's a lot of continue button hits. So it's really all about figuring out how to make the debugger not stop.
So, we now have added several new kinds of breakpoints here. We have conditional breakpoints. There's actions on the breakpoints, including automatically continuing, which most importantly will not actually cause Xcode to come forward or change which application is activated. So if you're debugging an event loop, you can actually do so now with GDB. There's templates and groups so you can organize and share your breakpoints as well with the important export. So, let's look at this in a little more detail.
Breakpoint actions. These are really cool. When a breakpoint is hit, now GDB and the debugging user interface in Xcode can take an action. And it's not just like, you know, do a little command. We obviously can send commands to the debugger and, you know, evaluate expressions or do whatever you need to do. But we can also log or speak text. So you can actually have your programs talking to you about what internal state they're in. Again, without activating Xcode. Great for full screen games and the like.
We can play sounds, play a system sound. We can run a shell script. So you can run a system command. So you can say move a file into the way or delete a file or do whatever you need to do there. We can also fire off an Apple script.
So you can go and tell some other program to do something or do whatever you can possibly imagine with Apple script. And now there's the visualization breakpoint, which we saw a demo of in Chris Espinosa's talk a couple days ago. Or was that yesterday? Time flies when you're having fun. And that'll highlight methods. And that'll highlight methods in the class diagram as they're hit.
Click. Breakpoint conditions. This is all about stopping or not stopping when you want to. GDB in the console has the ability to evaluate arbitrary expressions and print out the results. What a conditional breakpoint does is it allows you to evaluate the same arbitrary expressions, and if it returns true or returns false, it will either stop at the breakpoint or it will just ignore the breakpoint and life goes on. These really aren't true or false.
It's really zero and non-zero. You don't have to worry too much about the return. But you do have to cast the expressions, and that means the sub-expressions too. And if you've noticed, if you've ever worked with a GDB console, a lot of times when you go and you print something, it'll come back and say, hey, I don't know what you're talking about. I don't know what the return type is.
And you have to go and cast it to ID or cast it to Boolean or whatever. Make sure you do that, including the sub-expressions, as you can see in this example. And you can test these in the console. Anything that can be evaluated in the console with a print command can be used as a conditional for a breakpoint. And they're really powerful when combined with actions. Hey, only play the system sound on when this is nil, something like that. Breakpoint's conditions are supported for C, C++, Objective-C, and Objective-C++.
And that's just because of the capabilities of the underlying debugger. So now the templates and the groups. So now that we've got these really amazing breakpoints, we want to make sure that they weren't lost or forgotten. And you can group them together, and you can collect your breakpoints together. So you can have sets of breakpoints for particular kinds of debugging tasks. So you can have custom groups, and you can enable and disable entire groups with a single control click.
So if you need a set of breakpoints for debugging, say, the rendering of an NS view, hey, you can do a whole set of them, enable, disable all at once. If you need to do something down at the model layer, you can do the same thing. As well, you can have what are called template breakpoints. And with a template breakpoint, it's simply a breakpoint that provides a template.
That is available in the context menu in the gutter of your source code. So you can just create a new breakpoint of that type anywhere you want to very easily. And the customized templates are just done by creating a template breakpoints group. Now, of course, in Xcode, this means we've got the ability to import and export breakpoints, as well as the configurations, as well as a couple other things. So please, I want to see a healthy eBay market of all the settings in Xcode. Or at least post them somewhere.
So with the watchpoints, OK, watchpoints are the ability to watch a variable, watch a hunk of memory, and cause a breakpoint to be hit when it changes. They're slow. They're really, really slow. But they're really useful. The reason why they're slow is the way this works is it uses the Mach memory manager to mark the pages that the variables reside upon as read only. And then any time a write happens to one of those, the page fault's handled by GDB. It goes, was that something I'm really interested in? Yes. OK, great break. No. OK, let's mark the page writable. Write to it, then mark it as readable only again.
So yeah, this slows execution down a lot, depending on how often you're hitting the pages that are being watched. And it's really kind of a last resort. But again, it is very, very useful. And it's nice that you have it. It's simply accessed by Control-clicking on one of the variables in the variable. It's a variable viewer.
And it's very dangerous to watch values on the stack. In particular, what will happen is if you go into a system call, so you're down in kernel land, and the kernel goes, let me write to the page of memory that's now read only, the kernel doesn't like that. So it'll just kill your app.
And the watches are automatically removed, stack watches are automatically removed when you return from the stack frame, which we consider to be a convenience feature because that would mean the next time the stack grew below that point, you'd be watching random things. It's probably not what you want.
And you can also watch ranges of memory. So as an example here, I have set up something that can actually watch the contents of an NSMutable data, such that if anywhere in your code the contents of that data changes, the actual bytes that are stored within it, it will break in the debugger. And not only will it break, but it also pops down this really nice little sheet that will actually tell you what bytes changed inside the mutable data.
So that's pretty handy. It's not the prettiest bit of syntax, but we wanted to emphasize that it's not just about the GUI. There is a console there, and it's really powerful. So, as a demo of all these wonderful breakpoints, I will introduce Chris Friesen, debugging engineer extraordinaire. Thank you, Bill.
Breakpoint actions are a great way to instrument your application, and you can do it right in the debugger. And see, we already have demo one here. And what I have here is I have Sketch running in the debugger already. And I'll go down here and I'll Control-click in the gutter to bring up the contextual menu.
And we'll go to the built-in breakpoint actions that we have. So we have a whole selection here, and I'll go ahead and select sound out and continue. Because I'd like to do is I'd like to find out sort of how often did change is getting called in my application when I'm modifying objects in Sketch.
So now we'll go draw a shape. We get a whole bunch of clicks the first time on a mouse down, which might be a little surprising because you might not have expected did change to be called that many times. And then we get two more. I couldn't count how many happened the first time. So let's go ahead and use a logging breakpoint.
[Transcript missing]
So as we draw another shape, on mouse down we see this got called seven times. Mouse up, we get two more hits.
Now we can add a breakpoint. There's a shortcut to editing your breakpoints, and that is if you double-click the breakpoint, then you automatically go to the breakpoint window, and you're editing the actions. So we got these logs whenever did change was called. It would be great if we had some backtraces to go with that so we could explore why did change was actually getting called what the code paths were. So we can add a breakpoint command here. And we can use gdb command backtrace, and we'll only print the first five stack frames.
Now we go back to Sketch. And you can see on the console that we're generating back traces here for each time that we hit Did Change. And then we get a couple more on mouse up. So from there, you can go through the console. You can explore those stack traces and figure out whether or not any of those did change calls were extraneous, whether you can optimize anything there.
Now these template breakpoints are great, but you'd also like to be able to customize them. So to do that, we'll create a new group using contextual menu, and we'll call it template Now these template breakpoints are great, but you'd also like to be able to customize them. So to do that, we'll create a new group using contextual menu, and we'll call it template Now these template breakpoints are great, but you'd also like to be able to customize them.
So to do that, we'll create a new group using contextual menu, and we'll call it template So here we have a breakpoint, and it's got an action. The action is visualize, and I've unselected the open debug visualizer, because I only want the variables to be updated in-- Xcode's debugger interface. And then we'll auto-continue after a pause of one second.
Xcode's debugger interface. And then we'll auto-continue after a pause of one second. So we'll call this show variables and continue. Now when we go back to our code, and we'll go to set bounds. And when I control click in the gutter again, I get our menu, and here we see our template breakpoint is appended to the bottom of the contextual menu.
As I go ahead and change the size of the shape,
[Transcript missing]
As I go ahead and change the size of the shape, Now that's great, but it's hard to get at those values because they're simply displayed and they're lost. So we can go ahead and create a different kind of breakpoint action. We'll use the contextual menu, and we'll use the shortcut add and edit. So this adds a fine line breakpoint and immediately opens up the breakpoints window, and we're here editing the commands. So we'll tell it to auto-continue. And we can print bounds.size.
Now that's great, but it's hard to get at those values because they're simply displayed and they're lost. So we can go ahead and create a different kind of breakpoint action. Everything's being logged to the debugger console. Xcode does not come forward to interrupt us. We can interact with our application. It's all good.
Everything's being logged to the debugger console. Xcode does not come forward to interrupt us. We can interact with our application. It's all good. Everything's being logged to the debugger console. Xcode does not come forward to interrupt us. We can interact with our application. It's all good. And I added that to the wrong one. So I'll add it to an actual breakpoint in the project breakpoints group here. Bound.size.height greater than 200. Bounds.size.width less than 50. And we'll also add a sound again.
Bounds.size.width less than 50. And we'll also add a sound again. Now you can also add breakpoint actions to your symbolic breakpoints. So if we type a symbol name here. You'll notice that the enabled checkbox is in fact just a dash. And this is a visual indication that your breakpoint is pending.
G2B has not been able to find a symbol called "dialoog." Another reason that your breakpoint might be pending is that you might have asked for a symbol that hasn't been loaded yet. For instance, you might have asked for a symbol that's in a bundle or that's in a zero link file that your code hasn't executed yet.
So this is a good visual indication for if you wonder why your breakpoint hasn't been hit and it's still pending, then either you might have entered the wrong symbol or GDB simply doesn't have symbols for that particular file. So let's go ahead and we'll add a valid symbol.
And it turns out there are many dialog symbols in our application. And so now we get a pop-up, and it shows us that SKD graphic dialog and this object dialog is in here. All kinds of classes have this symbol. So we can go ahead and choose the exact one that we want.
And now we can see that this is set on SKT Graphic dialog. And it's set-- since we're running ZeroLink, it's actually set in the file, sktgraphic.ob. And with that, we hope that Breakpoint Actions are becoming an exciting new addition to your debugging in Xcode 2.1. Back to you, Bill.
Thank you, Chris. Yes, so actually I'm only going to be here for a brief moment to introduce Chris Hansen, who will discuss unit testing and demonstrate it. Thanks, Chris. Thank you, Bill. So Xcode 2.1 now includes full support for unit testing. What does this actually mean, though? Well, I'm going to tell you.
First of all, we're going to talk about what unit testing is. We're going to talk a little bit about why you should write unit tests. And we're going to talk about what this support that I've been talking about all week actually means, what it includes. So first of all, what is unit testing? Well, unit tests are small, isolated tests of individual software components. They're very tiny. They test one thing. That means they can do one thing well, and they're very easy to get right.
Since they're so small, they're also easy to make run fast. And since they run fast, you'll run them regularly. You can run them as regularly as every time you hit Command-B to build your project. and they provide sort of a scaffolding for your code. So if you've ever seen a building being built, the construction workers first put sort of a skeleton around the structure that they're creating. The scaffolding lets them work on the structure without disturbing the structure itself.
And that's really what your unit tests are. The difference is that when you ship software, you don't have to tear that scaffolding down. You can ship your application to your users and keep your scaffolding on your development machine and run it every time you make a tiny tweak to your application, every time you add a new feature.
So why should you write tests? Well, the number one reason is to prevent bugs from creeping in and causing regressions. We've all had this happen, and it really sucks. I mean, you know, you change one thing over here, something over here doesn't like it because suddenly you violated some expectation that that code had when you wrote it two years ago that you've completely forgotten about. Unit tests help you document those expectations.
That also lets you much more rapidly change your applications. Because all of your expectations are documented in code that runs every time you build, You actually have sort of something-- you have an alternate memory. You have the computer acting as your memory for you. So you can go through and make much more far-reaching changes to your code base as you refactor it than you otherwise might be able to.
And by doing so, they act as an executable specification. So this is great for prototyping. For example, if you're developing frameworks like Apple has been, you can test out these new APIs that you're creating, see how they feel, get a feel for how they might react under different circumstances and how you might use them before they even exist. And by doing this, you'd really be doing test-driven development. You'd be creating your unit tests that specify your software before even creating the software itself, so you know when you're finished.
So what does Xcode unit testing actually consist of? Well, we've got testing frameworks and tools included. For Objective-C and Cocoa, we've included OCUnit from Cente. And for C++, we've included a brand new framework that we created ourselves called C++Test. We include target templates for creating what we call test bundles.
And we'll talk a little bit more about those in a couple minutes. We include file templates for creating test cases for both OCUnit and for C++Test. And we have a shell script build phase in our test bundle target template that runs your unit tests as part of your build.
And all this really does is just invoke a run unit tests script that's in developer tools that takes care of all the heavy lifting of running your tests. You can look at the run unit test man page to see how this all works. And if you want, you can take a look at the script.
Unit test errors are all reported via the build results, just like your compile and link errors. So you make a syntax error in your code, it gets reported via the build results. If you try and use something that you're not linking against, it gets reported in the build results. And if you violate some of your code's expectations, and it causes a unit test to fail, that gets reported via the build results.
So, test cases. We'll start bottom up. You build your unit tests as classes called test cases that descend from a common base class. And your unit tests themselves are implemented as methods in those test cases. The test case exists to set up and tear down a common environment that we call a fixture around your unit tests. So, one fixture gets set up and torn down around every invocation of a test method.
In fact, it goes a little further. One instance of a test case is also set up and torn down, allocated and deallocated, around every set up and tear down of a fixture, which is set up and torn down around every invocation of a test method. It's a little recursive, but what that does is it serves to isolate your test code from the testing framework's code. That means that if there's a bug in your code, it doesn't affect the run of the rest of your tests.
And also, if there's a bug in the test framework, it doesn't affect your tests as much. Test methods use assertion macros, which we've provided a bunch of. There's a whole bunch in OCUnit that are really great. Plus, we have one that serves as a base for you for C++ tests. And these assertions just verify the state of your code. You can verify that objects are equal. You can verify that scalar values are equal. You can verify that variables are nil. And you can output messages when they're not.
[Transcript missing]
Test cases get grouped into what we call test bundles. And this is sort of a new technique. There's sort of a traditional way of testing applications where you create an application target, then you create a clone of it, and you put your test cases in the clone along with all of your application code. The problem is that you have to keep these in sync. So by grouping your unit tests into test bundles that are separate from your application or framework code, you've isolated the code under test from the code that's testing it.
This means that you don't have to worry so much about synchronizing the two, you know, making sure that when you add a resource to your application that you also add it to the version of the application that includes the tests, and so on. It also makes it easy to test both your debug and release builds because you can actually take your application as deployed to your customers, build your unit test bundle, pull it over, and then run the tests against it.
Of course, your test bundle needs to link against the code that it's testing. So for frameworks, this is really easy. You just link against the framework and it picks it up. For applications, this is a little bit more involved because you need to specify the bundle loader. When you specify as a linker flag the bundle loader for a bundle, What it'll do is it'll tell the bundle, hey, if you have any unresolved symbols at link time, check this executable to see if they're resolved there. And then when you're loaded by whoever loads you, look for those symbols in your loader. This means that you don't actually have to include all of your application classes in your test bundle. And we provide a target template that does most of the work for you.
It even includes a shell script build phase that'll run your unit test as part of your build. The only thing it won't do is actually link against the code under test. You need to do that yourself because, you know, we don't know if you want to test an application or a framework.
So what do we do for Objective-C testing? Well, like I said, we provide OCUnit, which is a very mature, robust framework for unit testing. It's really great. And it's very easy to get started with. We've even published an article on ADC already about using OCUnit to test drive your development. It supports testing applications and frameworks, and you can also test other types of binaries. You just have to do a little bit more work and use the OCUnit API directly.
rather than rely on automatic infrastructure to do it for you. Our run unit test script knows how to run test bundles, the end and dot OC test, as collections of OC unit tests. And since Objective-C is just a superset of ANSI C, you can also test your C code by writing tests using OC unit.
For C++, we created our own unit testing framework that's very, very lightweight and very easy to use called C++ Test. It follows the standard star unit testing pattern, and like I said, it's very, very lightweight. It supports applications and frameworks. Just like OCUnit, the tests are run by the runUnitTest script. The only difference is that your test bundle, when using C++ test, needs to end in the extension.cptest. There's a little bit of difference in how the tests get run, and runUnitTest knows how to take care of that.
Since C++ is mostly a superset of C, you can generally test C code too. And C++ test is not just built universal. It's actually built 64-bit too. So since you can't have a 64-bit application, you can really only have 64-bit tools, we can't actually run your unit test from the run unit test script when you're building a 64-bit tool with tests. You generally will have to do the clone to target and add tests to the clone technique to test 64-bit, but you can test 64-bit. And since it's a C++ framework, it requires you to use GCC4.
But what are we doing for other languages and other frameworks? Well, we provide a simple infrastructure for you to develop and hook in your own test tools. We provide a test rig build setting in the unit test group in the Xcode build settings that lets you point to a custom tool to run. This tool just gets passed as its first argument a path to the test bundle that you want to run. And all the details about the environment that this runs in are in the man page for run unit tests.
And like I said before, you can actually take a look at the run unit test script, too, to see how this all works. On error, all your test tool needs to do is generate output in a GCC compiler-like format. So if your own test tool wants to put a warning in the build log or wants to put an error in the build log to indicate a test failure, all you have to do is just emulate what GCC outputs. And let me show you how it works over on demo 2.0. here.
I've just got a very simple project. It's a simple framework with one class. Let me open this up here. And it's just an introducer. All it knows how to do is say hello. So what I'm going to do is that first I'm going to add a unit test bundle target and show you how to configure that for testing a framework. So I create a new, I create a, actually, I'm just going to control click on this and add a new target. Under my new targets, under Cocoa, I have a unit test bundle target that's new in Xcode 2.1. I'll just add that. I'll call it unit tests.
And finish this here. And Xcode brings up my info panel, which will let me set a dependency. So what I'm going to do most of the time is I'm actually going to build my unit test target. I'm not going to build my framework target. Instead, I'm going to let my unit test target build my framework target for me. The only other thing that I'll need to do here now is making sure that my unit test target is selected and links against my framework product. So all I have to do is click the target membership checkbox in here.
And my unit test bundle is now linked against my framework under test. So the next thing I'm going to do is create a new file. I'm going to control click on my project here and add a new file and add a new Cocoa Objective-C test case class. I'm going to add it to my unit test target, and I'm going to just name it unit test as well.
I'll just delete the extraneous header here. And we don't actually need to put anything in our class here because C++ is a dynamic language. We don't actually need to declare our methods. So let's save and quit that. Whoops. Get rid of the extraneous header. And I'm just going to create a test method. These are methods that have no return. Begin with the word "test." And then just have a name. So I'm going to create a save, say hello method. I'm going to create an introducer.
Of course, once I'm done, I'm going to release it. And now I'm just going to assert that this introducer does what we expect it to. So I'm going to assert that two objects are equal because I'm going to compare some strings. There are separate comparisons for scalar values, which you can generally compare using double equals in C, and object values, which you really need to compare by sending as equal.
Introducer, say hello. And I'm going to output a message in case something goes wrong. When, if a unit test fails, not only will it inform me what the failure was, but it'll also print this message. And this message can be any format string. You can have any number of arguments because it takes advantage of the variable arguments extension to GCC's macro system.
So now I'm going to save this. I'm going to import introducer.h2 just to make sure that I don't get any compile errors here. Now I'm going to bring up the build results, change my window size, and build. It's going to compile my framework first. Of course, it's got to precompile everything. Now it's building my unit tests, and it built and ran them. Well, it says build succeeded. It didn't provide any other output, but let's see what's in the build transcript.
[Transcript missing]
Now I'm going to add another unit test for a change that I want to make for the framework. I'm actually going to add this before I make the change because I'm going to let my test tell me how to change the framework. So I'm just going to add another test.
So test say hello to me. I'm going to add a little bit of variability to my introducer. I can just copy and paste this, or I can clone this out into a setup method and put it in a fixture. Of course, I have to tear down that fixture.
Of course, I need to put this variable declaration in my header file. That means I can get rid of some code in my tests. Of course, I have to forward declare the class. And finally, I'm going to change say hello to me to say hello, Chris. And I'm going to change the invocation.
[Transcript missing]
And you can see that my build, even though it succeeded here, which means that it actually built all the binaries that it was supposed to, it still got an error. And this is a unit test failure. It actually raised an exception.
[Transcript missing]
And of course I have to use NSString string with format here. And of course I should also declare the method to make sure that I don't get any warnings.
So let's just put it above there. Now let's build. It just built my framework, it built my unit tests, it ran my unit tests, and if we bring up the build results, we can see now we just passed two unit tests with zero failures. So that's test-driven development.
What it lets me do now is actually do a little bit of refactoring too. I've got a bit of duplication in the introducer here. I've got something that says hello to whatever I pass it, and then I've got another thing saying hello world. Well, I can just make the first call the second.
And since I have these handy unit tests, I can just build again by hitting Command B. And it just built my framework, built my unit tests, ran my unit tests, and they all passed again. So I know I didn't break anything by making that change. Let's go back to the slides.
And now Bill is going to give us some more information. It's really bright, so I need a shield. So yeah, more information. Documentation sample and other code, the developer.apple.com site, WWDC section, there's lots and lots of resources there. The documentation team, they have been very, very busy people. There is lots and lots of new documentation, not just reference materials, but concept guides too. I mean, you saw this thing.
You know, there's going to be a lot of read the manual answers on the mailing lists. Now, other sessions. Well, the subversion control, that would have been after this one, but then they moved this one. So, yeah, right. You missed that one. But there's Power User's Guide to the Apple Developers Connection. Tremendous wealth of resources there. Go to this session to learn how to use it.
There's also Project Management Mastery with Xcode. That's on Friday. Is it on Friday? Yeah, it's on Friday. So that's the real guts of the way that Xcode projects are being developed. Projects are managed in the way the builds work. And then there's a Development Tools Feedback Forum. And in general, if you want to provide feedback to us, bugreport.apple.com. And with that, who to contact? Matthew Formica, mformica at apple.com. He's our Developers Tools and Cocoa Evangelist.