Developer Tools • iOS, OS X • 53:46
Xcode 4 introduced schemes: a powerful way to control how you build, debug, test, analyze, profile, and deploy your application. Discover how to efficiently test your project, get a greater understanding of the Xcode build system, learn to use static libraries with iOS, and see how to configure your project for maximum productivity.
Speakers: Rick Ballard, Mirza Garibovic
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.
Welcome to Working with Schemes and Projects in Xcode. I'm Rick Ballard, and with me today is Mirza Garibovic, and we're going to take you through how to build and work effectively with your projects in Xcode. Whenever you want to build or perform an action with your built product in Xcode, use a Scheme to do it. A Scheme is an object that can build your product and perform the five Scheme actions on that product: running, testing, profiling for performance, analyzing your source code, and archiving for distribution.
When you select one of these actions, Xcode will build the targets needed to produce your product and then perform the selected action on your product. So Schemes are the tools that you use to configure and drive all your actions in Xcode, and that's why they're the central topic of today's presentation.
I'm going to start out today by covering the core concepts: workspaces, projects, targets, schemes, and run destinations. This may be review for some of you, but these are the fundamentals that you'll need to understand the rest of our presentation. Then we're going to dive into some use cases to help you get the most out of the five scheme actions.
We'll tell you how to work with your build products and how to customize their location. Then go over how to manage your schemes and share them with your coworkers. Finally, we're going to finish today by showing you how you can work with static libraries for iOS projects. So let's get started. We're going to go into the core concepts.
Our first concept today is workspaces. A workspace groups together references to projects and the related files that you want to work with together into one workspace document. Generally, you'll create a workspace for each distinct set of projects that you want to work with together. And when you group projects in this way, they share some important things. Every workspace gets a unique symbol index, a location for build products, saved window state, and build logs.
Furthermore, a workspace allows implicit dependencies to be resolved between targets in that workspace. If one target produces a product that another target in the same workspace links against, Xcode will discover this and it will automatically build the target that builds the library before the target that links against that library.
There's one more thing you should know about workspaces in Xcode, and that's even if you haven't created an explicit workspace document, when you open a project in Xcode, you always have a workspace. If you just open a standalone project, Xcode creates an implicit workspace for that project, and will actually stash a workspace file inside the project wrapper.
So anytime you're working with a project, you have a workspace, which includes a unique symbol index, locations for build products, and more. Any time in this presentation that I say something about a workspace, you can assume that the same thing is true about a standalone project as well.
Now let's talk about projects and about build configurations. A project contains references to your source files and resources, targets which process those source files to produce your product, and schemes which build those targets and perform actions on their products. Projects also contain build configurations, which allow you to specify different values for your build settings for debug and release over any other custom configurations you've created.
Every project comes with debug and release build configurations. And most build settings only have one value, but some, like your optimization level, will have a different value for debug and release. Most of you can get by with just debug and release build configurations. But if you need to vary your build settings for some other purpose, you can create another build configuration to do so.
You get at your build configurations in the project editor. First, you select your project in the project navigator on the left, then select the project in the source list in the middle. The project editor will be on the right and contains your build configurations. Now let's talk about targets.
A target is the set of instructions for processing some source files and producing one build product. So your target will reference some or all of the source files in its project, and it contains some build phases, which are the ordered sequence of steps that the target will take to produce your product.
There are a set of build rules which tell the compile sources build phase how to compile those source files. And there are a set of build settings which control just about every aspect of the build process. Targets can also have explicit dependencies on one or more other targets.
So if a target depends on another target, the target depended on will always be automatically built whenever the target that depends on it is built. You edit your target settings by selecting the project in the project navigator on the left and then instead selecting the target in the source list in the middle.
In the target editor on the right, if we select the Build Phases tab, you will see all the build phases, the ordered sequence of steps, that are taken to produce this target's product. So let's talk about a few of the things that you might want to do here.
If you want to make your target explicitly depend on another target, you can add it in the Target Dependencies build phase. If you click the plus button here, you'll see all the targets in this target's project, as well as all targets in projects that this target's project has a reference to.
So if you're not seeing the target you want to depend on, make sure that this target's project has a reference to the project that contains the target you want to depend on. The Compile Sources phase allows you to set per-file compiler flags, and also lets you just see and control which source files this target will compile to produce your product.
If you want your product to link against some libraries or frameworks, you do that in the Link Binaries with Libraries phase. You click the plus button here, we'll show you all the libraries available in your chosen SDK, as well as all libraries produced by targets in this workspace. You can also drag in any library or framework that you have a reference to in your workspace just by dragging it in from the project navigator.
If you're building a framework, you can control which headers wind up in the public and private headers location inside that framework with a copy headers build phase. Any headers you leave in the project section here will not be copied into your built framework and will instead only be available to the project itself.
If you want to copy some image files or other resources into your product, you can do so with a copy bundle resources phase, which copies the selected resources into the resources directory inside your product bundle. Or you can use a copy files phase to copy resources into any other location within the product or your build directory.
Finally, if what you need to do to build your product isn't covered by these other build phases, you can always use a run script build phase to do anything that a shell script can do. You can reorder this phase among the other phases to control exactly when it runs and have as many of them as you want.
If you select the Build Rules tab, you'll see the default set of rules that we use to control how the compile sources phase works. Most of you don't need to ever do anything here, but if you do have some special type of source file that you need to compile in a special way, you can set up a build rule to do it here.
Finally, we have the build settings. You'll notice that most build settings only have one value, but there are some, like optimization level, that you'll have set with different values for each configuration. If you want to vary a build setting's value across different configurations, you can click on the disclosure triangle to the left of the build setting. And if you want to set further conditions per SDK or per architecture to vary your build setting further, you can hover over the build configuration here and click the little plus button that shows up.
Now, there's one more important axis of configuration with your build settings, and that's the levels. If you click on the Levels tab, you'll see the complete hierarchy from which your build settings are inherited. On the right are the default values that will be used for each build setting if you don't override them. And to the left of that are the project-level build settings. If you go back to the Project Editor, you'll notice that there's a Settings tab there, too. And any build setting values that you set at the project level will be inherited by all of its targets.
The Target Level Build Settings is where you go to set build setting values that apply to just this target, and it will override the values set at the project or the default. And finally, on the left, we show you the resolved value that will be used for every build setting in the target.
You'll notice that there's some green boxes drawn here. We draw a green box around the level from which the final value of each of these build settings is being taken, so you can just see at a glance where your actual build setting values are coming from. So that's targets. Let's talk about the schemes that build them.
A scheme is a file that contains the instructions for building a set of targets to produce a product and then performing actions on that product. So we have actions for running, testing, profiling for your performance, analyzing your source code, and archiving for distribution. For each of those actions, the scheme contains a specification of the targets that will be built when you perform that action.
You can get at your schemes in the upper left-hand corner of the Workspace window, in the Scheme pop-up. When you click this, you'll see a list of all the schemes available in your Workspace. And if you choose Edit Scheme, we'll bring up the Scheme sheet and let you edit the settings of your selected scheme.
When you want to perform a Scheme action, you can do so from the Product menu in Xcode's menu bar, or you can click the button in the upper left-hand corner of the Workspace window. And actually, if you hold this, we offer the four Scheme actions that you often want to run repeatedly by clicking often, which are Run, Test, Profile, and Analyze.
And you'll notice that I haven't mentioned build as a scheme action. Build is not one of the actions. Building is actually a step that always happens before a scheme action. So when you're building, you're always building for some purpose, like I want to build my product to run on the debugger, or I'm ready to build my product to archive for distribution.
The action that you've chosen here specifies how you build, and that it can choose which targets you're building from the scheme and which build configuration they should build with. We do offer a plain build command in the product menu, but that's actually just a shortcut for build for running, which is the most common reason you want to build.
In this Scheme Edit sheet, you'll notice that the five Scheme commands are listed on the left, but at the top above them, there's a section for Build, and that's where you go to configure how your Scheme actions build in the Scheme. The scheme lists all the targets that the scheme will build, along with checkboxes that determine which scheme actions build which targets.
So, you know, most of your targets you want to build for all five scheme actions, but sometimes you don't. So, for example, if you have a unit test bundle, you really only need to build those tests when you're going to perform the test action, so you only need to have that checked for the test action here.
You notice that some of these checkboxes can't be disabled? Well, if a scheme action is actually using the product of one of these targets directly, like, say, your run action is running the application product of your application target, then that target must be built for that scheme action, and you can't disable it without unsetting the scheme action using that product.
We have some other options here. By default, Xcode will try to parallelize the build of all your targets as long as it can with respect to dependencies. If for some reason you need Xcode to instead build each target listed in your scheme in series, you can turn off parallel builds. Also, the implicit dependencies feature I mentioned earlier can be turned off here if you don't want Xcode to automatically find and build targets that produce libraries before it builds the targets that link against those libraries.
You noticed that build configuration was not there in the build slice. That's because each action specifies its own build configuration. For example, when running or testing, you usually want your debug build configuration. But when profiling or archiving for distribution, you usually want your release build configuration. If you select an action here, like the test action that I've selected, you'll see a control that lets you control which build configuration is used when performing that action.
So that's schemes. Now, when you're performing a scheme action, you always have to tell Xcode where you want to perform it, and that where is the run destination. Run destination encompasses the device that you want to build for and run on. So we give you a choice among the plugged-in iOS devices configured for development, all your available simulators, and the local Mac, depending on what your product supports. You'll note that you only see devices that are actually compatible with your target's base SDK and deployment target build settings.
You get at your run destinations in the upper left on the right-hand side of the scheme pop-up, and when you select that, we'll show you all available run destinations that are compatible with the targets in your selected scheme. That covers the core fundamentals. Now I'd like to invite Mirza up on stage to walk you through some use cases for the five scheme actions.
So I have a project here, and suppose I'm an iOS app developer, and I'm getting ready to ship my latest app. I've had my coworker Rick here testing it for me, and he just sent me an email about a crash. I ran my app and tried to reproduce it, but I couldn't, so I asked Rick to send me his application data.
The application data represents the saved state of my application on a particular device, and Rick collected it from his application, from his device, by using the organizer, which you can find here in the window menu. In order to use the application data on my device, I'm going to want to add it to my workspace.
So first I'll extract this. and then just drag it into the project navigator. I'll copy it into my project, but I don't actually want to add it to a target because it isn't part of my application. Next up, I need to configure the run action to use this app data every time it runs my app.
I can get to the run action by going through the edit scheme settings here, but since I'm going to be running right after this, there's a better way to do that, and that's to hold option when I click on run here, or hold option and click on the run button right here. So let's do that.
So here's the edit scheme sheet showing me the run settings. I have a bunch of options here for what I'm going to run and how I'm going to run it. And I'm going to skip right here to the Options tab and look at the application data. The pop-up shows all the app data packages in my workspace, and here's the one I just added. You'll notice that because I held down Option, there's a Run button here in addition to a Done button. So I can click to run right here as soon as I made my changes.
So Xcode's launching my project in the simulator. So I have a bit of a stopwatch app. It's preloaded with Rick's data. I can still interact with it. And Rick's told me that if I just add and remove a few items, I should be able to reproduce the crash.
Now, Xcode's running my app in the debugger, and when I crash, it can tell me what happened where I've crashed. You can see right here that I'm somewhere in, obviously, Message Send, but this isn't really a useful crash point. Now I noticed this code has some weird memory management practices, and it hasn't been switched over to Arc yet. So I suspect I might be using a dangling pointer or maybe sending a message to a deallocated object. The tool we usually use for that is NSZombies.
Now, let's configure Xcode to help me do that. If I option click on Run again to go back to the Scheme options and switch to Diagnostics, you can see that I have a bunch of options here. I can enable these with checkboxes instead of having to know the underlying environment variables that control these in the OS. You might want to enable all of these, but they all have pros and cons, so look inside our documentation for what they are. I'll enable zombie objects, and let's run again.
So Xcode relaunched my app, reloaded the application data, and what I'm going to do is play around and see if I can reproduce that again. And here we go. Now, I'm still getting assembly here, but if I look into the debug console, I can see a message about a message being sent to the allocated object.
That's definitely a zombie, right? If I look around in this stack frame up here and go up to my code, I can see where in my code I've crashed. So I'm sending a message or I'm trying to print a message with this object's array. That seems pretty harmless, but if I backtrack a little bit up here, you can see that I'm trying to save some memory by releasing this array when it's empty.
But that leaves me with a potentially daggling pointer in the object's IVAR, and the rest of my code isn't set up to handle that. So I could set this to nil and then compensate elsewhere in my code, but you know, this seems an awful lot like premature optimization to me, so I'm just going to delete this block of code altogether. Okay, let's try again.
Fresh state. And that seems to work. It's not crashing the same way. All right. Let's move on to the test action. If I click and hold here, I can change my current action to test, and I'll option click to go straight into the settings. You can see here that all my tests run under a debugger, and here are my application tests. Now, my app's actually using a static library as well, and I'd like to run those tests any time I run my tests. If I click on plus here, I'll see all the tests in my workspace, I can add my library tests, and let's test that right away.
So Xcode's running my tests, and I can immediately see that I already have a failure right here. If I go to the log navigator, I can see the tests as it's running them. Now, I have a lot of tests here, and they're going to take a while to run, so I'll just stop this. I want to focus on the test that failed, and I can actually set up Xcode to use just that one test, so I can quickly iterate on it.
I'll option click on test again, and I can expand these groups, and option click here to enable and disable everything, then I'll disable the library tests and the test I saw failing. If I just test again here... I'll get that test failure and none of my other tests are running. Now, if I click on this, I can bring it up in the source editor. So here's my test code.
This seems like a little bit of a strange place to fail, so I'm creating a new account with the name John, then I'm checking that the new account name is John. That should just work, right? Now, every time I run my tests, Xcode is running them in the debugger. So if I want to take a closer look, I can set a breakpoint right here and hit Test again.
Okay, here we are, stopped in the debugger, and I'm just going to step over this line and expect the results. So if I look at the variables view right here, I can see that this account is nil. It seems a little strange. I should have gotten an account. So I can go to the debug console right here and print the error.
So I'm being told that an account named John already exists and the account creation failed. So actually you can see my test case set of code right here. I already created an account with the name John. There's a collision, right? I still want to test account creation separately, so I'll just change this name to Johnny and test again.
So I stopped at my breakpoint. I can remove it and continue on. And my test succeeded. All right. That's how you can configure Xcode to control exactly which tests you run and then debug the tests you're working with. Let's move on to the profile action. If I click and hold here, switch to profile, Xcode launches instruments for me.
And I have a bunch of tools here that I can use to measure the performance of my app. That's generally what you would use profiling for, to measure performance issues. Now, Rick's been testing my app again, and he told me that if he plays with it for a really long time, it crashes eventually.
That sounds like memory growth, right? The first thing I like to check for is leaks, and I can select, re-select here, and profile. But if I'm going to be working with leaks a lot, I can actually set up Xcode to launch leaks directly for me. So let's cancel that, go back to Xcode.
I'll option-click on profile, bring up the edit scheme sheet, and you can see that I have an option right here to select the instrument I want to use. There's leaks. I'll change that and hit profile again. So here's instruments. immediately opened up Leaks, and it's running my app in the simulator. So I'll play around here a little bit and see if we can see anything.
So if you look down here, you can see there seem to be leaking NSDate objects. Now, my app's actually showing dates, so that kind of makes sense. So I'll stop this recording and look at the data. Instruments telling me that the responsible stack frame is in my insert new object code. And if I click in the toolbar to bring in the full stack, I can double-click on my stack frame and see my code.
So I'm creating a date with alloc init, then adding it to an objects array, and this isn't Arc code, right? So I still have ownership of this date, and I need to release it after I'm done with it. That's why it's leaking. Now I can go back to Xcode and fix this profile again, but there's another way I could have caught this before I even started Instruments, and that's the analyze action. Let's go back and try that.
So I can click and hold here to switch to analyze. When I run the analyzer, Xcode runs the Clang static analyzer on all the source in my targets. The results are similar to compiler warnings, but the analyzer employs more thorough algorithms that reason about the semantics of my code, and it can track down leaks like the one we just saw.
So I click here to analyze, and that was pretty quick. I have an issue here. I can click on that. I'm being told that I might be leaking an object stored into the variable date. If I click on this annotation, I get a control flow graph. So by the time I get to step two right here, it's telling me I'm no longer using date, I've leaked it. So I can fix that just by adding date release right here.
Okay, let's analyze again, see if that helps. Okay, so analysis succeeded. There are no more warnings. So I fixed the crash, I fixed the leak, my text cases are all good, there are no more warnings here. If I'm ready to ship this app, I'll use the archive action. So when you archive, Xcode builds an optimized release build of your product ready for distribution. First of all, I'll change my destination to a device because I want to distribute something that runs on a device. Then I'll go up to the products menu and click on archive.
So here's the organizer. Here's my new archive, but there's a bit of a problem here, right? It's not showing my application icon. Instead, I'm getting this generic default archive icon. And Xcode's telling me that the archive type is a generic Xcode archive. There's no version number. There's no identifier. This isn't something I'm going to be able to submit to the store.
So to see what's wrong here, we're going to have to learn a little bit about the structure of archives. Let's switch back to slides for a moment. So what is an archive? It's actually a bundle, and it contains three things. An install style build of your application, which has post-processing steps applied to it to make your application ready for distribution. It contains your app's debug symbols so that you can symbolicate crash logs and debug other things. And it also contains metadata about your submission status, comments, and notes you might want to record.
There's a specific type of archive called an application archive. The definition is that it contains nothing other than your application. So your build process must take care not to install anything else separately into the archive. A common problem with apps that use frameworks or libraries is that they accidentally install the framework and library next to the archive in addition to inside their application.
So what you want to do is just embed frameworks in your application, link in the libraries, but make sure they aren't installed in the archive. And for that, we have a skip install build setting. So let's go back to my project and see if that's what's going wrong.
So let's dig into this archive, take a close look at the inside. Here's a bundle, I right click on it. Here's a bundle. I right click on that to show the package contents. Here are the things I mentioned. Here are my debug symbols, an info with metadata and a products directory with my project products. There's my application right there. And here's my straight static library. It's not supposed to be in here; right? Now, I can't just delete this from the archive. I need to go back to Xcode and tell it how to create a new archive without that library.
If I look inside the project navigator, I can see that I am using a static library right here. And if I go to its target's build settings and search for I see that I've set that to no, but it's really supposed to be yes. So let's change that in Archive again.
So here we go. Here's an archive. It has my app icon. Xcode recognizes it as an application archive. It has a version number and a bundle identifier. So that's how you can look inside Archive to fix common problems and get your app ready for distribution, making sure that you're not shipping anything you don't expect.
So that covers the basic scheme actions. The one you'll use mostly, day to day, is the run action. It also has the most useful options. So I'm going to demonstrate a more advanced use case that shows you how to use custom executables and environment variables or arguments based on build settings. Suppose that I'm working in a plugin for a Mac OS X application.
The way plugins usually work is that the plugin is a bundle or dynamic library, and the host application is developed separately but is then instructed to load the plugin dynamically at runtime. It needs to know where to find the plugin in order to load it. So I've created an app here from the template, the bundle template.
And I can build this, test it, analyze it, but I can't actually run it or profile it because a standalone plugin doesn't really do anything. What I need to do is tell Xcode to automate the act of launching the host application, telling it where to find and load my plugin, and then connecting with a debugger so that I can verify my plugin actually works.
And I can do all that in the run settings. I'm going to option click on run again. and go to the Info tab. You see here that I have a control for the executable I'm running. It's empty because my plugin has no executables, right? What I can do here is select Other to find my host application.
Now, I have an app here called Quux Arcade. It acts like an arcade cabinet. It loads plugins to provide the games that it later shows. I'll select that, and let's just run it and see what happens. So Xcode's building my plugin, and it's going to launch Quark's Arcade attached with a debugger, but Quark's Arcade doesn't know where to find my plugin yet.
So I'm getting a message in the bug console, no plugins found. I need to figure out how to tell Xcode where to find my plugin. Quark's Arcade is instructing me that I can pass extra plugins path in the environment, and this is how I'll tell it where to find my plugin. I'm going to copy this right here. Now, when Xcode builds my plugin, it puts it in its build products there. I need to tell Quark's Arcade where my build's project is, and also do that in the run settings.
If I switch to the Arguments tab, I'll see a section for environment variables. Create a new one and paste in extra plugins path. Now, how do I know where my build product store is? Xcode actually provides it in a build setting. This way I don't have to know where it is in case I move my project or reconfigure it otherwise. So what I'm going to do here is click and use $brace build product store.
And the $BRIS or parentheses syntax tells Xcode to expand this build setting in this value when it launches the host application. If you look at our documentation, you'll find a list of useful build settings. The only thing left is to tell Xcode which target to get that build setting from, and I'm going to select Spruce Blasters right here. So let's run again. So here's Quark's Arcade and it found Swift Blasters. Great. So that's how you can use custom executables and environment variables from build settings. All right. I'd like to hand over to Rick to tell you more about how to work with your build products.
Thanks, Mirza. I'm going to show you how you can work with and customize your build products. But first, let's talk a bit about where they go. Every workspace has its own derived data directory, and that's where we put derived data, your source code index, your build logs, your save Windows date goes in there, and your build products and intermediates by default. Workspaces are distinguished by path, so if you have multiple copies of the same workspace, they actually get their own derived data directory because they're at different paths.
So I'm going to show you how we can dive in and customize where this derived data goes. Great. In Xcode, if you go to Xcode's preferences, There's a Locations preference panel, and here you can see that there's a derived data setting. This allows me to control exactly where my derived data goes. And if I click this arrow, Xcode will bring up the derived data location in Finder.
Here you can see I've got a bunch of different folders with kind of a long gibberish suffix after that. Well, each of these folders are each workspaces per workspace derived data directory. And they're named after the workspace they apply to and have appended a hash that's generated from that workspace path. So it can disambiguate which one goes with which workspace. If I look inside one of these, you'll see that I have build products and an index and stuff like that.
So one thing I might want to do, you know, by default this goes in my home library folder, but one thing I might want to do is put it in a secure location, like an encrypted disk image, for security reasons. So it's actually really easy to do that. Let's go back to Xcode here, and I can choose a different location for my derived data. So I'm going to choose custom, and I'll bring up the file chooser, and I've got a secure disk image here. And I'm going to name this one Secure Derived Data.
Now if I choose this, all my derived data for all my projects go into folders, per workspace derived data folders, at that location instead of in my home library folder. I might also want to set this on a per-workspace basis. If, say, different workspaces need to go into different secure disk images, like maybe I'm a consultant and I have different clients who have different security needs.
So I'm going to set this back to default and close my preferences. If I go to the File menu, I can choose Project Settings, or if this is an explicit workspace document, it would be called Workspace Settings. And here I get a sheet that gives me the same control, so I can set a custom drive data location on a per-workspace basis.
Okay, another thing I might want to be able to do is find my build products so that I can easily manipulate them directly or inspect them. So, you know, say I want to use the NM tool to inspect the symbol table of my build product. Let me show you how to do that. First, I'm going to launch Terminal.
And I'm going to type xc run nm. xc run-- whoops-- nm. xc run will find nm in my installed Xcode and launch it out of there. And I'll pass -a to say I want to dump the whole symbol table. And now I need to pass it the path to my build product. So let's figure out how to get that in Xcode.
If I go back here and click the Advanced button, I'll get a sheet that gives me direct control over exactly where my build products go. It also has these fields down at the bottom that show me the path to my build products and my build intermediates. And I can just click this button here to bring up the build products folder for this workspace.
You can see I've got a per configuration build directory in here. And there's my build product. So I'm just going to drag this right into terminal to paste in its path. I'm also going to supply the path to the binary inside this bundle. And there's my symbol table. That was pretty easy.
Okay, another thing I might want to do is share build products. As I said, by default, each workspace gets its own derived data directory with its own build products so that one workspace doesn't get mixed up with another. It doesn't clobber another workspace's build products or accidentally link against something that was produced by a different workspace. But sometimes I actually want to intentionally share build products between multiple projects or workspaces. So, for example, let me close this down.
I have my Smith Blasters plugin here, and it goes into the build products directory, and Quarks Arcade knows to look for it there now. But I have another plugin project called Super X Code World, and I want Super X Code World to also get picked up by Quarks Arcade at the same time when it launches, so I load both plugins.
Since Quarks Arcade is just looking at my build products directory, that means they both need to be in the same build products directory, both plugins. So there are a couple ways I can do this. The first way is to put both projects into the same workspace. So I'm going to go ahead and create a new workspace, and I'll call it Quarks Arcade plugins. And I'll just stick this on my desktop.
And now I can go ahead and drag in both the Spiff Blasters project and the Super Xcode World project. And now since both projects are in the same workspace, they're both going to use this workspace's derived data directory, as long as I have the workspace open here. And that means that their build products are going to go in the same build products directory.
So I can select my Super Xcode World scheme and just build. So that'll put its plugin in my build products directory. And the same Spiff Blaster scheme that I set up before is available through the workspace because it's in that project here. So when I build and run, it'll still launch Quark's Arcade and it's still set up to look in my build products directory. And you can see Quark's Arcade now finds both plugins in the build products directory at once.
Okay, what if I want to do something similar, sharing build products, but I don't actually want to have to put all the projects I'm working with into one workspace and have them all open at once? There's a way to do that, too. I'm going to close my workspace and open Spiff Blaster's backup. Go back to File, Project Settings, Advanced, which is where I get direct control over where my build products go. You see, one of the options here is Shared Folder.
When I choose Shared Folder, my workspace will put its build products into a folder of the name I specify here inside the derived data location, instead of into this per-workspace derived data folder. So I can call this shared folder Quark's Arcade Plugins, and if I were to open Super Xcode World and set Shared Folder there to the same folder, then both these workspaces would build into the same folder, and Quark's Arcade would be able to find both plugins the same way. I can also set this. Globally, for all my projects, if I really want to, in Xcode's location preferences, the same way.
Okay, I'm going to show you something else now. Often you may have an external tool or script that you want to run on your build products outside of Xcode, but the problem you run into is, how does that tool know where to find your build products, especially if it's running on an automated server or something like that? There are a couple different ways you can tell external tools how to find your build products. The first way is to choose a location where you're going to place those build products yourself and tell Xcode to put them there, and then tell that external tool the same location.
So if I choose custom here, I'm going to choose an absolute location, and I'll use the file picker under products to choose my secure source disk image, and I'll create a products folder here, create that, and choose it. And now this workspace will put all its build products at that location. So since that's a path that's known to me, I can just give that path to my external tool or script, and it will know how to find my build products there too.
Well, what if I don't want to abandon per-workspace derived data directories, because I'm working with Xcode, multiple copies of this workspace, and I don't want them to clobber each other, but I still want to give my path, my products path to an external tool. There's another way I can do that.
I'm going to leave this set to the default location and dismiss this sheet, and I'm going to bring up terminal again. And to do this, I'm going to use Xcode build to print out the build settings of this target. So I'm going to say Xcode build and pass the dash show build settings command line flag.
Now I need to pass dash project and go to the build settings. I can give it the path to my project. So I'll drag in spiff blasters to paste that in. Now I need to say -target and say which targets build settings I want to see. So it's the Spiff Blaster's target.
Now when I hit return, Xcode build prints out all the build setting values from this target. So if I do a find here and search for built products dir, you can see that it's printed out the location where my build products are. So I could have my external tool or script run this same command itself and parse out the build products dir value from this output and look there to find the build products that it wants to find. Great. There's one more thing you often do with your build products, and that's to clean them out so you can do a clean build.
In Xcode, when you use the clean command, we will analyze the targets in your selected scheme and delete the primary product of each of those targets so that you start over clean. Sometimes this can take a little while, and the other downside here is it can sometimes miss files.
If you have a copy files build phase or run script build phase that lay things down in the products directory that aren't in your actual product, the clean command doesn't know how to remove those. So sometimes, both for speed and for thoroughness, it makes more sense to just delete your build products folder outright.
And I know a lot of you go and do this in Finder often. Well, there's a way to do it straight from Xcode, which is more convenient. If you hold down the option key, the clean command turns into a clean build folder command. If you choose that and confirm the sheet, we just delete the build products folder outright. All right, that's about enough about build products. I'm going to go back to slides. And let's move on to talk about how to manage your schemes and share them with your coworkers.
The first thing you generally want to know is when schemes are created. You can create them manually, but Xcode will also create schemes automatically for you under a couple of circumstances. First, whenever you create a new target or a new project with targets, Xcode will create a scheme for each new target.
Second of all, whenever you open an existing project or workspace that you haven't opened before, or that you have but someone's created new targets in it since you last had it open, Xcode will go and create a scheme for you for each of the new targets in that project or workspace. These auto-created targets are per user, so they're not shared with anyone else. And if you delete them, Xcode will remember that it's auto-created them for you in the past, so it knows not to auto-create them again, and so you don't have to play whack-a-mole with unwanted schemes.
I said we create a scheme for every target, but that's not quite right. For most targets, we auto-create a scheme for you, like framework targets, application targets. But if you have a unit test bundle target, we'll instead try to add that test bundle to the test action of another scheme.
So if your test bundle or unit tests for your application, we'll find your application scheme and just add the test bundle to the test action of that application scheme. If you create a new test bundle and you get a scheme with it, you usually want to delete that test scheme and just add the test bundle to the test action of whatever scheme the tests test.
If you have a large workspace, you may get a lot of auto-created schemes when you open it. Don't feel like you have to keep them all. For example, if I have a framework scheme and an application scheme, but my application links against my framework, I can go ahead and delete my framework scheme, because whenever I build the application scheme, it'll build that framework target as well via dependencies.
You can manage your schemes by going to the Scheme pop-up in the upper left, where you'll see all the schemes in your workspace, and choosing the Manage Schemes option. This brings down the Scheme Management sheet, which is where you can go to create, delete, reorder schemes, and to change some other options.
So, first of all, in the upper left-hand corner, we have an Auto Create Schemes checkbox. Most of you will want to leave this checked, but if you have a complicated workspace with complicated shared schemes that you've set up that you want your team using, and you don't want new team members getting all the auto-created schemes when they open your workspace because you want them to use your shared ones, you can turn this checkbox off, and Xcode won't auto-create schemes for them. The Auto Create Schemes Now button will cause Xcode to create a scheme for every target in your workspace that isn't already part of an existing scheme. So, this is useful if you've deleted a whole bunch of auto-created schemes, but now want to get them back.
So say you've authored a complicated scheme with some very specific settings, and now you want to share them with other people on your team. Well, if you check the Shared checkbox here, Xcode will move that scheme from your per-user data location to the shared data location inside the project, and everybody else who's working with that project or workspace will see that scheme too. Any modifications you make to the scheme at this point will also be shared with everybody.
Sometimes you may have a lot of schemes that are shared that other people on your team have created that you don't actually care about, like maybe some QA schemes if you're in engineering, and you don't want to see them cluttering up your scheme pop-up. If you uncheck the Show checkbox, we won't show that in the scheme pop-up anymore, but this checkbox is per-user, so you won't be affecting anybody else.
Sometimes you have a shared scheme and you want to make a quick one-off change to it, like add some argument to the run action. But you don't want to accidentally make that change for everybody if you forget to delete it. So what we recommend in that case is to select the shared scheme and choose Duplicate from the gear menu down here. That will give you a per-user temporary copy of that scheme. Well, it can be temporary, it can be permanent, up to you. And you can make your changes and then just delete it when you're done with it if you don't want to keep it around.
The container column here shows you where each of these schemes is stored. So, you know, every scheme file is stored inside a project or workspace wrapper, and you can choose to store your schemes in a project if you want everyone who works with that project to see that scheme, or you can choose to store them in a workspace if you want certain schemes to only show up for people who are using one of your specific workspaces.
So let's talk a bit more about exactly where these scheme files go on disk. Inside each project or workspace wrapper, there are two interesting directories: XE user data, which is all the user data for that project or workspace, and XE shared data, which is all the shared data. So inside the shared data directory, there's an XE schemes directory, and inside there are all the scheme files that are shared for users of this project or workspace.
In the XC User Data folder, you'll see a separate folder for each user of this project or workspace, named after the short username of that user. Inside there is another XC Schemes folder, and inside there you'll find all the per-user schemes, as well as an XC Scheme Management Property List, which stores things like which schemes you've chosen to show or hide, what order you've arranged them to show up in, and which schemes Xcode has auto-created for you in the past so it knows not to auto-create them again.
If you bring up the Source Control Commit sheet, you'll see the same hierarchy represented here. And generally, you want to commit all your shared schemes so that you can actually share them with your coworkers. You may also want to commit your per-user schemes in your Scheme Management Property List if you want to save those things.
By default, we don't add any schemes to Source Control. So when you want to commit a scheme, you need to make sure to check the checkbox on the left here to add it to Source Control. That's about all you need to know about Scheme Management. So to finish today, I'd like to invite Mirza back up to take you through a demo of how you can work with static libraries for iOS.
Thanks, Rick. Hello again. So what I'd like to show you here is how you can start with an application, extract your utility code into a static library, and then use that library in your other applications. So I have a project here called Hazard Map. If I launch this in the simulator, I can see it displays a map. I want to use this map view across my other applications as well. So the first step is to create a separate project for my static library. I'm going to go to File, New Project. Select the Cocoa Touch Static Library template, and I'll call it libmap.
For my application to use my library, I need to add a reference to the library project for my app project, and there's a shortcut that I can use to do that right here. If I select Hazard Map, I've created a libraries group for it. Now I need to take care not to put my library source inside my application source because I want to share this library across other applications as well. So I'm just going to put it up here next to my apps.
Great. So I have a static library here. It comes with some sample code that I can delete. And now I need to set up this library. I'm going to do three things. I'm going to copy source code into it, copy resources, and then figure out what headers I want my clients to be able to access.
So I want this view controller, this map, and this map view. And I'm just going to drag them up here into the library project. I do want to copy the source into my library source, and I want to compile these things, so I'm going to add them to my target.
I cannot delete them from my application. I'm going to trash them because I've copied them over. Next up, I want to copy some resources from my app to my library. Specifically, I have this mapping data and this view controller, Zib. So I'm going to create a group for the resources right here and just call it Resources.
And then I'll select these two, copy them over the same way. But this time, I don't want to add them to my target, because a static library target cannot process resources. Instead, I'll want to add these to my application target, and I'll show you how to do that in a moment. For now, I'll leave this unchecked.
And again, I can delete these. So the last step in setting up the library is figuring out what headers I want to export. I look at the library's build faces. If you've ever built a framework before, you know that a framework has a copy headers face. But copy headers isn't intended for use with static libraries.
If you have one, you should get rid of it because it will definitely mess up your distribution archives. What we're going to do instead is use a copy files face. If you're using the latest Xcode 4.5 developer preview, the static library template will include this. If not, you can add it yourself using the add build face button.
This phase is set up to copy my headers into my products directory in a sub-directory called include, and a sub-directory under that that's going to be my library name. Here I am using the same $brace syntax to expand product name into what's going to be my library name. So the header I want is this view controller right here. I'm just going to drag it in. So that should be it. Let me build my static library to make sure it works.
Okay, build succeeded. Let's go back to the app. So the next step here is to look at what configuration I need to make in my app to include the static library correctly. The first step is the reference to the project, and I already did that when I created a library. Next up, I need my app to link against my static library.
If I go to my application targets build phases, I have a link binary with libraries phase. I can click plus here to add my static library. When I do this, Xcode's implicit dependencies are going to build my static library every time I build my application, so I don't need to add an explicit dependency here.
Next up, I want my app to use the resources from my library, specifically the zip file and the mapping data. To do that, I need a reference to each of these resources in my application project, and what I'll do is create a brand new group. I'll just call it LibMap, and I'll drag these in.
This time, I need to take care not to copy them over because I want to pick up changes that are made in my static library. I will add them to my target so that they are copied into my application target every time I build. This is a bit of a manual process because every time I add a new resource to the library, I'm going to need to add another reference inside my application.
So now if I look at my application's Copy Bundle Resources phase, I'll see these two new resources, and that's what I want. Okay, now let's look at my app delegate. When my sources all live together, I could import my library headers or my view control headers directly by name.
But in this case, I'm getting an error, and the reason is that my application doesn't know where to find my library headers. Now remember how my library has a copy files phase that copies headers into the product directory under include under the library name. So Xcode's standard header search paths are already set up to look inside the product directory include, but I also need to add my library name here. So I'm going to import libmap slash hazard map view controller.
This way, each of my library's headers are nicely segregated by library name so that libraries that have similar header names aren't going to collide. There's one last step here. When you link against a static library, the linker is actually only going to include the things it can detect that you're using. That way it keeps your app small.
But Objective-C is such a dynamic language that you could potentially use a class without the linker being able to detect that you're using it. So I need to tell the linker to be really aggressive about including all Objective-C code from static libraries. I'm going to look at my application target's build settings. I'll search for other linker flags in all the build settings. And here it is.
So what I want to do is add -objc. And that's it. That should tell the linker to bring in all classes and categories from any static library I'm using. So what I'm going to do is a clean build folder to start from scratch and make sure we got this right.
And then I'm going to run my app in the simulator. Okay, here it is showing the map again, but this time it's getting the map from my static library. So this is how you can set up your iOS applications to use static libraries. What I'm going to do is close this and open up my other application right here and show you how to get started on setting that one up to use the same library.
I've created a libraries group right here. If I right-click on that, I can choose to add files to it. And I need to find my library project. And I don't want to add it to my target. I'm just referencing it here. And now we can go ahead and set up the linkage and the headers the same way I did before. All right. Go back to slides. So that's how you can effectively use static libraries in your iOS applications.
I hope you find this session useful. If you have any questions, please contact Michael Jurowicz, our developer evangelist. We also have excellent documentation available on developer.apple.com. And if you have any questions after the labs, you can always ask them on devforums.apple.com. Here's a few more related sessions, some of which are coming up this week, some of which already happened but you can find online. Thanks, and enjoy your WWDC.