Developer Tools • iOS, macOS, tvOS, watchOS • 37:03
Learn about all of the new, and a few existing, features of Xcode source editor. See how you can make the most of new code completion features, Swift image and color literals, and snippets. We'll also show how to add commands to the source editor with new Xcode Extensions that you can distribute on the Mac App Store.
Speakers: Mike Swingler, Chris Hanson
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
Hi everyone, welcome to using and extending the Xcode source editor. I'm Mike and I work on Xcode and I am the only thing standing between you and the beer bash [laughter]. So what are we talking about today? Well I'm going to start off and show you some great new features that we've added to Xcode 8, but that's not all. I'd also like to show you some really helpful features that are already in the existing shipping Xcode today that I think you probably don't even know about but I think they're really cool.
And I use them every day to quickly edit and navigate the Xcode source base. And just to be clear, everything I'm going to show you comes standard built into Xcode. Then, my colleague Chris, he's going to come up and show you how to go beyond just what we offer and further extend the Xcode source editor yourself.
You can write the extensions to make the editor do some of the things you've always wanted it to do and then you can even share those extensions with your friends, your co-workers, or even the whole world. The first thing you'll notice in the source editor, or if you just looked up and around today at the conference, is our new source code font, SF mono.
This is the mono space compliment to our system font San Francisco. Our font experts have explicitly designed SF mono for retina displays. And have carefully designed each of the glyphs used for code punctuation to be distinct and legible, even at small sizes. The next thing you'll notice as I'm just moving around here in the editor, is that we have this new current line highlight that shows you where you're at, at a glance.
Each of our built-in Xcode themes has a subtle, hand tuned highlight color, but it's also customizable. So you can change it to be as bold as you like or set it to completely transparent if it's not your cup of tea. So you're probably already familiar with our comment and uncomment command, command/ right? Well we've added a new command that you can get to just by dropping the option key along with command/ and that inserts a documentation comment.
[ Applause ]
So, this works directly on or above any method, class, struct, anything that you know is a structural code element. And, you'll notice that these little placeholders here, give you a chance to describe, you know your method, or discuss the parameters, the, whoops, the throws description or you know, say something noteworthy about the return value.
And what's really valuable is that these bits of documentation that you add in the code docs show up in quick help and at the bottom of the code complete, window. And also notice that we've provided our new SF mono font with more weights than just regular and bold. The documentation comment uses SF mono light while the keywords use SF mono medium which are just a little lighter and just a little heavier than the SF mono regular that the rest of the file is in.
Unlike most other mono space fonts, we've given SF mono a full spectrum of weights from light through semi-bold, all the way to heavy and we've included the italic variance. This keeps the spacing between each character even while letting you customize exactly how much emphasis you want. So in the next feature I'm going to show you I'm going to jump to another file, I'm going to use open quickly by pressing Command, Shift O, and typing in a few characters to get to my TimerViewController.
And you may have seen this before, this is the document items menu, and you can open this by pressing Control 6. It has all of the classes, structs, enums, properties, everything that's inside of that file. But that's not really the most interesting bit. Did you know that if you just start typing while this menu is up, it filters the items? And you'll notice, here that, this little fix me comment that I have, it actually has this little Band-Aid, bandage icon on it, which I thought was kind of cute [laughter].
So, jumping here to my load view method, and you can see here that we have an image in a color that we've been waiting for a designer to deliver to us. But I actually got those P&G's last night and I put them into the jogger asset catalog, which we can actually see down here in the library.
So this is for the Timer Button. So I'm going to go and I'm going to select to the end of the line here by pressing Control E, while holding down Shift. And I'm going to invoke code completion by pressing Command Space. Now watch carefully, I'm going to assign this image directly by using code completion. I'm just going to type t, b, to get my Timer Button.
And you'll also notice that code completion as in Xcode 7.3 now uses the same fuzzy matching algorithm that we use for open quickly, the document items menu, and even our new search in the documentation window. And you can see here on the left side, we show a little preview of the image so you know that you're picking exactly the right one. So, boom right there, in your source, this is an image literal.
[ Applause ]
So every image in the library, regardless of whether it comes from an asset catalog or it's just referenced in the project, it's available from the code complete window now in the source editor. So, the way this works is we insert a Swift specific entity into the source which is known to the Swift compiler, and it dereferences out to a call to UIImage image mamed.
So you can see all of your images now in place, instead of just the name string. And we have this for colors too. So I'm just going to select the end of the line here, and invoke code completion and type color. And what this will, when I hit Return here, you notice that I get this really neat color picker pop-up.
From here I can just arrow down, and pick a color like this, lovely green here. And what's really important to know is that all of these image and color literals are the, they're fully keyboard navigable. If I just hold down Shift and arrow back over the literal, and hit Return, I bring up the color picker pop-up again.
And, you know to which I can still arrow around, but if these colors that are in the pop-up aren't exactly what you're looking for, and there, it's not exactly what I'm looking for, you can use the recent colors, which are also shared with interface builder, or you can click on the Other button down here and it brings up the standard system color panel.
So in this case, I actually just want to sample out this lovely navy blue, right from the button since this is going to go with the button as I'm constructing it. But we still have a little bit of an error here, the color literal is, is a UIColor here, but the layer that we're assigning this to is a cgLayer and it's expecting a cgColor. So I can fix that real quick, like that.
[ Applause ]
Hopefully this makes it really clear, that image and color literals are really the fully typed, checked, real type objects in Swift. So the next thing I'd like to show you, is not a new feature and in fact, it's not even an Xcode feature. It's actually been part of the standard Cocoa Text system since before macOS 10.0, and that's the find pasteboard.
Now you may not have noticed before that if you've done a find perhaps by selecting some text, like say Pause here and then you copy it to the clipboard using Command C. And then you bring down the find bar with Command F and then you paste it in there with Command V. That that same search, shows up in other applications.
That's kind of weird isn't it? Well, that's because the find pasteboard is, it actually works across applications just like the clipboard that you all know. It lives in parallel with the clipboard and the two exist side by side. So there's actually a really cool trick that you can use if you have something that you want to hold onto in the clipboard and you don't want to blow it away. But if you still want to search, for something like start here, you can just press Command E, and it pushes it directly into the find pasteboard. And this allows you to then, hit Command G, and cycle through all of the remaining hits in the file.
Now, if you just want to quickly find and replace in the exact same file, we have a specialized command for that too. If you hold down Command Control E, you'll actually perform an edit all in scope, and this modifies every instance of the symbol in that file. So here I can just add ED to this method, because started sounds a little bit better. And, and this one's a real big time saver, and I use this a lot.
Now, if all you want to do is actually move some lines around, you don't even need to use the clipboard for that either. We actually have a dedicated command for that. So if we just select some lines here and hold down Command Option Bracket, you'll notice that I can actually move this entire chunk through in and out of if functions and other methods. And they just flow right through your code.
So this is all great if you're just hanging out in the same one file at a time, but if you want to do a find across other files, for example if I'm going to find all instances of timer, you can do that by holding Command F along with Shift to do a full project find. And here, I'll just push timer into my find pasteboard and search for that.
And, this was actually something that was pointed out to me recently, I don't have to click into the find navigator to actually start to arrow up and down these results, Command G also works here too. The only difference is I just hold down the Control key while I'm hitting Command G and it actually cycles through all of the results in all of my different files, including my interface builder documents and all of the instances and hits in there. And it works in reverse by holding down Shift, just like Command G does as well.
So, I don't know, I know some of you are actually kind of writing some of this down. But, you really don't have to because there's actually one place inside Xcode that has the full list of all of these commands and all their keystrokes. And that's inside of the Xcode preferences window.
So here, in the Key Bindings pref pane, you can actually do a search for anything that involves say, find, and you can see all the results. Also, if you kind of vaguely recall there may have been some sort of like reveal command that you, held down Command J for.
This actually searches across the keyboard shortcuts as well. So this is really powerful. And if you don't like our keyboard shortcuts, for all of Xcodes built-in commands, you can actually set your own from here. And, if our built-in commands are not enough and or they don't do that kind of just one thing that you've always wanted the source editor to do, I'd like to invite Chris up now to show you how you can add your own commands, Chris.
[ Applause ]
Thank you, Mike. As Mike said, I'm Chris and today I'm going to show you how you can enhance Xcode with source editor extensions. Now what we're offering you is a way to add your own commands to the source editor as part of the Editor menu. Your commands can modify the user's text, as well as selections within that text, say to perform navigation. And unlike some other types of application extensions, you can implement any number of commands as a single Xcode source editor extension.
Now we chose to base Xcode extensions on application extensions which are the basis for extensibility across all our operating systems. Since Xcode extensions are application extensions, each one runs in its own process and can do pretty much whatever it wants within its process without interfering with Xcode or with other extensions.
Of course, as application extensions, Xcode extensions are also sandboxed and use entitlements for doing anything that needs to escape that sandbox. And Xcode only gives access to Xcode extensions to the text and metadata that they need at runtime to perform their operations. They don't get access to the project structure, they don't get access to the user's files on disc.
And why are we doing it this way? Well, stability. We want to ensure Xcode is as stable as possible for all of our users. Also, security, application extensions are our way of allowing you to enhance both the operating system and now our tools while maintaining the integrity of the entire system.
And of course, we're also doing this for speed. Application extensions are built on top of Mach messaging and XPC and are fully asynchronous. So they can work as quickly as possible and not slow down our users. And, there's another reason too, that we decided to use application extensions as the basis for Xcode extensions, and that's so you can distribute them on the Mac App Store.
[ Applause ]
And just like all other application extensions, Xcode extensions are built into a host application. And this application is a great place to put your extensions preferences or configuration information. For example to control which commands a user actually wants to make available from your extension. And it's also a great place to put any other UI that you want your extension to provide.
Since all Xcode is going to do is give you a menu item for each of your commands. And being part of an application is what enables an application extension, an Xcode extension to be distributed on the Mac App Store. Of course, you can also sign your application and your Xcode extension with your developer ID and distribute it however you want.
[ Applause ]
Now, let's talk a little bit about how Xcode brings your extensions to life. To ensure the best possible performance, Xcode is going to find and automatically start your extension pretty early on in its own startup process. Before your users will need to use it. And source editor extensions aren't like some other kinds of application extensions that are only used once, and then shutdown.
Xcode will actually try to keep your extension alive as long as possible so that it can send as many commands as the user wants to invoke. Now when your extension is started, if it needs to do any work, at that startup, Xcode will send extensionDidFinishLaunching to it. And this is a good place to do startup work as long as it's quick.
That's right you need to do your startup work as fast as possible and that's so your users can get to your extension as soon as they want to. And to help you there, Xcode ensures that starting up your extension is asynchronous with starting up other extensions and with its own startup.
Now once your extension is started, Xcode will ask it for its commands, and its commands can come from one of two places. By default you'll have an entry in your NSExtensionAttributes dictionary in your info.plist that specifies all of the commands in your extension. But you can also provide an override of the commandDefinitions property from your extension class that overrides the values that are given back by the info.plist. So if your extension has a dynamic list of commands, say because it's downloaded some new JavaScript that it actually uses to run those commands, it can provide a new collection.
Now once Xcode has your commands, it'll give each extension its own submenu of the Editor menu when the user is editing source code. And the extensions are listed in alphabetical order just as they are in the finder so that they're all in a stable place for the user, from run to run of Xcode. However, because order of commands is often important and often, conveys a bunch of semantics, Xcode will preserve the order that you give your commands to it, and list them in the menu in that order.
Now to invoke a command, of course a user can choose the command from your menu item, or they can press a keyboard equivalent that they have assigned. Your command object will be instantiated and sent an invocation and a callback. Now the invocation packages up all of the data and metadata that your command needs to do its work.
And, the command then uses the callback once it's done its work to tell Xcode that it's done. And let's take a look at the actual APIs. Here we have the simple protocol that all of your command classes need to conform to. Just as I said, it's passed an invocation and a completionHandler call back.
And that invocation just carries a few simple pieces of data. It has a commandIdentifier which is also set in your info.plist or in you command definitions array. And that lets you distinguish multiple commands that are handled by the same command class. After all, there are a lot of commands especially in source code editing that do just slightly different things. So you'll probably want to implement a lot of things using the same command class and just handle a couple different special cases in each one. And this identifier lets you figure out which of the commands that the user has invoked.
We also provide a property that you can set, a cancellationHandler block on. And this cancellationHandler is called if the user cancels your command. And that can happen if your command is taking too long and we'll get a little bit into that a little further on in the presentation.
And finally, of course, the invocation also contains the buffer of text the user is working with. And that SourceText is represented by an instance of another object, XCSourceTextBuffer. And it has a bunch of metadata in addition to the text itself. We give you the uniform type identifier that Xcode thinks the file containing that text conforms to and that way you know if you're working with Swift source code, XML data, and ObjC++, header file, whichever form of text.
We also provide the indentation settings that Xcode has for that file. So that as you're making changes to the text in the file, you can also conform to what the user expects Xcode to do when it's indenting that file. Now because tabWidth, indentationWidth, and whether to useTabsForIndentation have some subtle interactions. We've also provided a bunch of details about how they work together in our header files. So I encourage you to check out that header documentation, see exactly how those all fit together.
Now, we provide the text that the user is working with in two different ways. If you need to process the text as a single stream, say by piping it to a command line tool, you'll probably want to work with the completeBuffer, which represents the text the user is working with, the entire file as a single string.
However, if you only need to make minor changes to the text, this is a really inefficient way to work since your extension needs to send that whole buffer back to Xcode. And that's we also provide this mutable array property containing the lines of text in the file. We found ourselves, that when writing tools that work with source code, we actually find a line and column abstraction works much better than just working with a single huge buffer of text. And this, by being a mutable array, also lets Xcode actually track what you're changing so that we only need to send back the individual changes you've made, we don't need to send back the whole buffer. And this can really help improve performance for editing extensions.
And in addition to providing the lines of text in the file, we also provide a mutable array for the selections in the file. There will always be at least one selection which is either the user's insertion point or selection and because Xcode's editor is built on top of the Cocoa text system, we support multiple selections as well. And if you want to change the selection all you have to do is change this mutable array.
And what's in that array are source text range objects. Now we use SourceTextRange instead of NSRange because we really do believe that the line and column abstraction that we provide is the best way to do most text editing. And that's why a source range instead of being a position and a length, is actually a start and end position and those positions are represented in terms of line and column.
Now, I'm going to show you a demo of how you can create your own Xcode extension. Let's go to the demo machine. And, let me get my notes set here. So I was really impressed by what Mike told us about Swift literals, the Swift color and image literals.
And I'd really like to use them in my own code. So I think what I'm going to do is create a new source editor extension that can automatically convert any uses of UIImage or UIColor to the corresponding Swift literals. I'm just going to create a new Xcode project, and I'm going to create a new OS X application. After all, my app extension, my Xcode extension, has to be carried inside an application. So I'll create this and I'm just going to call this Chris Literals.
And I'm just going to give myself an organization identifier of com.example, and press Next, then I'm just going to put this project on the Desktop. And now that I've created my project, I'm going to add a new target to it and I'm going to add an OS X application extension target.
And you can see we have our new Xcode Source Editor Extension template right here at the end of the list. And I'm going to name this, Chris Convert to Literals and just finish. And when I clicked Finish, Xcode offered to activate the target for me, just as with any other app extension. So I'm going to accept that. And now let's take a look at the code that Xcode generated for me.
In this convert to literals group, Xcode added a class that represents my extension itself. And this just conforms to the XCSourceEditor extension protocol. And it also added templates for a couple of, for a method and a property that I can uncomment if I really want to override these and provide my own implementation.
I don't want to right now, I don't think I need it for this particular project, so let's just take a look at the next file. In the next file SourceEditorCommand, I have my first command class. And just as I showed you it conforms to XCSourceEditor command, and has just a single perform within vocation method.
Now let's take a look at my info.plist that Xcode created for me. And you can see, in my extension attributes, Xcode added a set of XCSourceEditorCommandDefinitions which is an array of dictionaries. And that dictionary specifies the class to instantiate for a command, the identifier to use for that command, and the name that the menu item for that command should take.
So I'm going to change that to Convert to Swift Literals and just accept that. Zoom out, and go back to our command itself. Now I'm going to cheat a little bit here, because I've already written this code and I'm just going to use a code snippet it to insert what I've already written.
I've named it Chris Demo, and you can see that it's actually not all that much code. So what I'm doing is just looping through all of the lines in the file, and if the line actually has a UIColor or a UIImage invocation in it, I'm just replacing it with its corresponding Swift literal syntax.
And then, if the line has actually changed, that's when I replace the line in the lines array, that way I'm not replacing every line in the array, and I'm certainly not modifying the complete buffer. I'm only modifying the lines that actually need to be. And I'm also keeping track of the lines that I modify, so that later I can also construct a set of selections to represent those lines. I set those selections, and then I just invoke my completionHandler to let Xcode know that my command is done. I see Xcode in the suggested applications.
And if I click Run here, what do you think is going to happen, beyond of course building my extension. Well you can see we launch another instance of Xcode for you to test your extension against. And we actually provide a little bit of visual distinction here too, we darken the icon in the dock and we darken the icon in the, welcome to Xcode window, so you know at a glance that this Xcode is one in which you're testing an extension.
Now I'm just going to open up Mike's jogger project because I noticed he had some, uses of UIImage and UIColor that weren't converted yet. And you can see another case where we've modified the UI in the activity view up at the top to specifically indicate, at a glance, that this Xcode is being used to test an extension.
And Mike's left me actually looking at some cases of UIImage and UIColor that I think would look nicer as literals. So I'm just going to look in the Editor menu, I see my Chris Convert to Literals extension, and I see my convert to Swift literals command. Now if I go back to my original Xcode, and just set a breakpoint, say down here.
If I actually run my command now, you'll see that nothing happened and that's because I'm stopped at that breakpoint in the first Xcode's debugger. So you can actually bring all of the power of Xcode's debugger and LLDB to bear in debugging your extensions as well. I'm just going to continue from that though. And I've still got my breakpoint in there, let's get rid of that. And if I go back, I can see the Xcode highlighted all of the lines that I've changed, just as I told it to, and it's converted everything to using the new Swift literal format.
[ Applause ]
Now let's say I actually want to do this on a pretty regular basis. Well it's easy enough, I can make that pretty fast by just adding a key binding for my new command. I do that in Xcode's preferences in the Key Bindings, and I'm going to search on the name of my command which I know starts with Chris.
And we're right there in Xcode's Key Bindings, I think I'm just going to name this, command option control/, that's easy to remember right [laughter]? Now let's go back to the slides, and talk about something that's a little near and dear to my heart when working with Xcode extensions, and that's speed.
Text editing is a user synchronous activity. Your users are really going to want to keep their hands on the keyboard and just type as a continuous stream, even while they're invoking your extensions. And your extensions really shouldn't prevent them from just continuing on with their typing. Now in order to prevent any race conditions between your extension and the user, Xcode actually locks out changes to the document that the user is working with when they invoke an extension.
And fortunately, this means you don't have to worry about reconciling the changes you've made and the changes the user made. On the other hand, it means that if your extension takes too long, which we define as a couple of seconds, the user's locked out from editing their document.
So, what do we do there? Well we give the user the opportunity to cancel. And we do that by bringing down a cancellation banner. And I think the system's trying to tell me that this slide is taking a little too long so, let's cancel out of it and go on.
Now Xcode helps keep things fast for our users by starting extensions early and keeping them alive as long as they're likely to be needed so it can send commands as soon as they're invoked. And as we talked about earlier, using the lines array ensures that your extensions data transfer is also optimized for performance.
And when a user needs to cancel a command, that cancellation is actually immediate on the Xcode side. As soon as the user hits Cancel they can continue typing. Now your extension will still receive that cancellation and still needs to react to it, but the user doesn't need to care.
Of course there are a few ways your extension can help Xcode with performance as well. Your extension can startup as quickly as it possibly can so it's ready as soon as the user is. In implementing your commands you can use GCD in all of our asynchronous programming patterns to ensure you're making maximum possible use of the user's system and getting back to Xcode as quickly as possible. And of course, you can avoid replacing the whole buffer of text if you don't have to. And finally, as I said, you need to handle cancellation quickly. Because until your command finishes handling its cancellation, it won't be available to the user again.
Now today Mike showed you a bunch of great new features in the Xcode source editor, like our ability to add documentation comments and our support for Swift color and image literals in code completion. And he also showed off some features that were very recently added but are already shipping in Xcode 7.3, like fuzzy code completion. And I showed you how Xcode source editor extensions work and how you can make them yourselves, I can't wait to see what you do with our new API. There'll be some more information available at our page on the WWDC 16 website.
And there are a couple of related sessions that you can check out on video, in particular Optimizing App Startup Time doesn't just apply to apps, it also applies to app extensions because they use a lot of the same technologies. Our Introduction to Xcode is also great for people coming up to speed on the Xcode environment and what your users will expect when you implement extensions.
And finally, we have some sessions from previous years talking about what it means to be an app extension and best practices in implementing them. And we'll also be at the bash tonight, so, thanks for coming to WWDC and if you have any questions for us, you can see us there.
[ Applause ]