Enhancements to LLDB simplify life for Swift and Objective-C developers alike. Experienced developers will find new powers within the debugging console, and learn more about the connection between the debugger and Swift REPL. Newcomers will get insight into the range of possibilities hidden behind the LLDB prompt.
KATE STONE: Good afternoon and welcome
to What's New In LLDB.
My name is Kate.
KATE STONE: And I manage the runtime analysis tools
in Xcode, including LLDB.
I will be getting us started here but I'll be bringing up two
of my engineers, Sean and Enrico,
to dive into the details in just a little bit.
But to get us started, I would like to talk a little bit
about the year's highlights, some of the changes,
big and small, since the last time we were at WWDC.
Most notably we shipped our first Swift debugger.
It is a step forward, obviously, in being able to write
and develop the Swift code that you have come to know and love.
But more importantly it wasn't our last step.
We continued to improve that debugger
and we hope your continued feedback will help us make it
even better still.
At the same time we shipped the Swift REPL;
it's a little bit different
than your standard request response REPL environment,
in that it is really LLDB in disguise.
The REPL is not just a way that you can ask questions in Swift.
It is a way that you can debug those questions,
you can actually set breakpoints in the REPL
and do everything you'd expect
from a debugging environment because it is LLDB.
If you haven't dug deep into that,
I'd recommend you take a look at the blog posts on the subject.
But, of course, that's not the end.
There are numerous improvements that we have made since then.
Since we first shipped, we've made over 100 improvements
in the Swift debugging experience, and we continue
to enhance the debugging experience
for Objective-C as well.
We're going to cover some of them here,
but just to recap some of the changes that have been made
since last year, Swift types initially did not show inherited
We've since corrected that.
Help now includes command aliases.
And it is important to understand these,
because while LLDB has what may look like a wordy syntax,
we might ask you to type 'help,
in practice there are a number of short cuts.
And throughout this presentation we'll use the following notation
to describe them.
Help can actually be abbreviated to the first unique sequence.
So in this case H is enough to bring up help.
More importantly, for more elaborate examples
like expression-O-- , which means 'evaluate an expression,
tell me the results as if it were an object
by sending it a method to describe itself,
and then no more options followed by the expression,
but you can just type PO.
PO is a handy alias that is shorthand
for everything to the left.
So, as you get used to the help, you should learn
to use the aliases, because they are going to be your fastest way
to get to some of the powerful functionality in LLDB.
We've also improved data formatting, because telling you
about your data is absolutely critical
to your debugging experience.
So types like set and NS Index Path now have default formatting
to help you understand your data instead
of just showing you the raw dump
of the underlying representation.
Printf prototype for expressions,
if you've ever tried to evaluate an expression involving printf,
you may have seen some slightly suspect results.
That's because the debugger tries to make a lot
of assumptions about your expression for any declaration
that it doesn't know,
and it didn't know printf, in most cases.
So it didn't know that it was a variatic function, that is,
one that can take a variable number of arguments,
and didn't know especially on 64-bit devices how
to pass those arguments correctly.
That has been corrected so that all
of your expressions involving printf for C
and Objective-C should just work right out of the box.
And then lastly, and truly lastly, this time,
we have improved the disassembly format
to make it easier than ever to read.
Digging into some of the more interesting enhancements,
breakpoint enhancements were made this spring
that you may not be aware of.
Specifically you can now name breakpoints.
And it may not seem obvious why a named breakpoint would be
easier to use than any other breakpoint, until you realize
that those names don't have to be unique
and that you can apply multiple names to a single breakpoint.
You can think of them a lot like tags.
And all breakpoint commands take those names.
So I can set a breakpoint by providing breakpoint set
and dash N, capital N followed by a name, optionally followed
by another dash capital N and additional name, and so forth.
But more importantly, once I've given a breakpoint
or multiple breakpoints a name, I can then operate on them
with all the other commands.
Breakpoint enable name will enable all breakpoints
that share that name or, again, think of it as that tag.
That is made even more interesting by the fact
that we now allow you to set breakpoints in LLDBinit.
If you're not familiar with LLDBinit,
this is a filename starting with a prefix with a period that goes
in your home directory that tells LLDB, here are a bunch
of commands to execute every time I start an LLDB session.
So if you use these, you can create a set
of default breakpoints when LLDB starts,
and all breakpoints you set before you actually create a
target are inherited by every target you create.
So combine these two, and I can write an LLDBinit along
A set of breakpoints, -n everything named malloc,
a breakpoint -n everything named free,
name all of those breakpoints memory, and then disable them.
And what this gives me is a handy way to get all
of my memory handling breakpoints just
by typing 'breakpoint enable memory' in any session
that I use thereafter.
So, you'll have your own set, I'm sure, of handy breakpoints
that you always want to have access to
and now you can give them easy to remember names.
But, of course, you are here not just to learn about things
that we have done and already shipped,
but the things we are just starting to preview.
Xcode 7, really important release,
a lot of improvements here.
An expression evaluation, notably.
And Sean is going to talk to you a lot more in depth about this.
Swift 2 support, because, of course, the language continues
to evolve, as well as Objective-C support in terms
of advanced handling for modules.
Some of this we'll cover in depth,
but some of it is really just behind the scenes.
If you go digging, you may notice, for example,
that in Xcode 7, because we know about modules,
we can actually build debug information for the module once
and then not replicate it everywhere else.
This allows us to drastically decrease the size
of debug information and improve the performance of compilation.
Once you get into actual .dSYM file,
that .dSYM file will then contain absolutely everything
But it also contains some handy other optimizations,
like not duplicating information for C++ types,
thanks to the one definition rule,
whereas before we may have had multiple copies.
In fact, we have seen debug information winding
up a sixth the size that it was in Xcode 6,
especially for C++ projects.
We have also improved the data formatting in a variety of ways.
Vector types now get unique automatic data formatting both
for Objective-C and Swift.
And, perhaps most importantly, if you have custom types
in Swift, you can now customize the way they are presented just
by writing Swift code.
And Enrico will talk about this in depth.
We have integrated support for address sanitizer into LLDB.
So not only will address sanitizer tell you
when you reference memory that is invalid,
but you can ask questions about memory.
You can ask, for example, for the history of memory to see
where it was allocated or when it was deallocated,
right from the LLDB console, so the memory family
of commands are ones you might want to dig into
and look at the help for.
We've also added type lookup commands.
The type lookup command will allow you to get information
about any type in the system.
Basically it's a header-like representation
that you can get right there in the debugger to remind you
about the contents of the type.
So, from the LLDB prompt,
all I need to do is type 'type lookup' followed by a type name
and I'll get a quick description of that type.
In this case the new error type that's used
by Swift's error handling mechanism.
I see that behind the scenes we have a pair
of properties normally implemented for you so long
as you're using a EDAM type,
but nonetheless you can see the details right from the console.
Similarly if I'm interested in a type
like 'Comparable,' a protocol.
It will tell me that this protocol actually derives
from two others, so that you will see that Equatable is
where we get the equality operator.
The underscore Comparable is where we get the basic less-than
and then the derived operators are part
of the Comparable protocol.
So there's a lot of convenient information you can get here.
And like everything else, remind you that you don't have
to type the full command 'type lookup.'
TY space L is sufficient to use this command.
But to dive into more depth, especially to talk to us
about how we evaluate expressions,
I would like to invite up Sean Callanan [applause].
SEAN CALLANAN: Hello.
I want to tell you about compilers in LLDB.
Now you may be saying
to yourself there are lots of compiler sessions.
Why do I care about compilers?
Compilers are a critical part of LLDB.
They are what makes LLDB powerful,
and they are what makes LLDB easy to use.
It is really powerful because compilers have a unique
understanding of the way your program works, the way it lays
out its data, and what you mean when you say I want to look
at this variable or call this function.
Another reason the compilers are so important is
because they make it easy to work with the debugger.
If you want to print something you just use the code you are
used to typing, pass it to the expression command,
and the compiler takes over from there.
So today I want to tell you
about the improvements we've made
in two important areas related to our compiler integration.
First of all, I've got some great news
for you long-time Objective-C developers.
And then I'm going to tell all you Swift developers,
whom I hope is all of you at this point,
about the improvements we've made there, too.
Let's get started on Objective-C.
Now, LLDB contains two separate compilers.
Clang, a powerful Objective-C compiler and, of course,
since last year, the Swift compiler.
Our Objective-C compiler support has been improving
over the years and we have been adding great new features.
For example, the Objective-C runtime integration.
So if there's some information in the Objective-C runtime
that isn't present in your debug information, we know to feed
that to the compiler so that you can use lots of classes
without having to do anything special.
Last year, of course,
we introduced the Swift compiler into LLDB.
And the Swift compiler already is a very powerful tool.
And we've used a lot of lessons that we learned
from integrating the Swift compiler,
and we are improving them both together.
Let's talk about how the expression parser works
with Swift, with a view of Objective-C.
Let's look at a simple print command.
Now I say print here because I'm using the p shortcut.
What that actually means is expression.
Notice the dash dash.
That means everything after the p command has to be code.
You can't pass extra options to the command this way.
There are other ways, as Enrico will tell you,
and as Kate showed you before.
Now here is some simple code that runs through a loop
and prints the loop counter each time.
And, indeed, if you run it you get the numbers
out that you would expect.
Why is this cool?
Well, LLDB and your program are separate processes.
LLDB has a swift compiler inside it.
Your program is already running, but with LLDB's help,
the Swift compiler can inject the code that you just typed
into your program to run.
That's kind of cool if you like printing loop counters.
But there's a little bit more to debugging.
Swift also works with your variables.
You type in some code.
And the contents of an array are printed.
Now, this array here is just a piece of data
that is in your program.
And LLDB arranges to show that data to the compiler
so that it can generate the code that you expect.
There is another thing I want to talk about really quickly.
And that is how Swift works with the SDK.
When you type an expression like NSApplication.sharedApplication,
what happens is, first
of all you see the NSApplication.sharedApplication,
the way you would expect.
But what LLDB is doing is going out and finding the SDK module
that contains that, giving the compiler access to it,
then the compiler finds NSApplication and figures
out how to use sharedApplication.
That's all great and it is automatic in Swift.
In Objective-C it doesn't always work.
So let's try all these things in Objective-C and some
of you may be getting a little bit apprehensive at this moment,
because you know that trying to NSLog something isn't as easy
as it sounds in the debugger.
Well, in the past when you've typed in NSLog,
you've seen errors like this: NS log has unknown return type.
That's because in the SDK, sure enough,
there's a definition of NSLog.
But what LLDB sees is only what's
in the debug information in the symbols.
In this case, all it sees is a symbol.
That symbol, we don't know what its return type is
and we don't even know that it takes a format string.
Well, the good news is, we fixed that [applause].
SEAN CALLANAN: NSLog works the way you would expect.
Now, let's try something a little bit more underhanded.
Wait a second!
Why is this underhanded?
It's right there in the frameworks.
Unfortunately it looks like this identifier doesn't even exist.
Now, you may know that if you use NSLog
and you cast the result, yeah, you can use that.
But with NSMakeRect you can't even do that.
That's because NSMakeRect is defined NS-Inline.
There is no symbol for it.
What LLDB sees is nothing.
No problem anymore.
SEAN CALLANAN: Just one more pain point left.
Now let's look at the old NSApplication
Sure, if you ran that expression by yourself, yes,
it would work in Objective-C.
I mean, we have been working
on the Objective-C runtime integration.
But if you try to get out the undo.Manager you quickly see
that the runtime doesn't tell you everything.
In particular you get this weird error
about undo.Manager not being found on an object of type id.
What is going on there?
In the SDK, sure enough,
sharedApplication returns an NSApplication star.
But if you look at the runtime, what it returns is id,
a generic Objective-C object.
That is no longer a problem.
In fact we see information that we can only find
out from the SDK such as that that pointer is nullable.
This is some of the great new SDK support for Swift
that just bubbles down into Objective-C as well.
But the information is right there.
And that's the philosophy that we've applied here.
Read straight from the SDK.
Your code has always worked in LLDB.
We knew about local variables, functions, your own classes.
SDK functions, on the other hand,
we had a little bit more issues with, as you see.
SDK classes like NSView and NSApplication, yeah,
we saw them but, as you see,
because of the runtime integration,
there was a little bit of an asterisk there.
Now, SDK constants, if you ever tried using
NSASCIIStringEncoding in an expression,
you know that never flew.
And if you tried using macros like int-max, and, you know,
max, to take the largest of two numbers,
that wasn't going anywhere either.
Well, all of that is fixed in the latest LLDB.
SEAN CALLANAN: And we got rid
of the annoying asterisk, too [laughter].
SEAN CALLANAN: Now, you may say,
how many easy monthly payments of 39.95 do I need to make
to get this functionality?
Well, good news, it is all free
and the way you do it is @import AppKit.
You just run an expression
that says import the frameworks I care about,
if you pull in AppKit, or for the two
or three iOS programmers among you, import UIKit, this works.
Now, we haven't been standing still with Swift either.
Swift 2.0 has great error handling support
and LLDB is right off at the bat supporting
that just the way you would expect.
We can handle Swift errors and you don't need to call 'try'
when you are calling functions
that could throw errors in expressions.
The reason is because we catch stuff for you.
If you type this function 'throws' and pass
that to an expression,
notice this is the same as the p command.
You will get an error variable created for you
that contains the error that that function threw.
You can also do this in the REPL.
If from the REPL prompt, you do the same thing,
you get an error variable.
Now, let's look at a little bit more detail of LLDB support.
And that is, you don't always want
to see what the resulting error was.
You want to know what was the code that threw that error.
Well, the way you've done this
in Objective-C is you used breakpoints.
Specifically you set breakpoints on Objective-C exceptions.
The way you did this was you used the breakpoint set command.
You specify that you want to set the breakpoint
for the Objective-C exception,
and we set that breakpoint for you.
Now whenever your Objective-C code tries
to throw an exception, we'll stop.
You can do the same thing with Swift errors.
Just replace the Objective-C with Swift
and we'll stop whenever your program wants
to throw a Swift error.
But there's another cool thing that you can do.
And that is you can stop on specific types of errors.
This is something we support in Swift.
And the way you do it is very simple, it's very similar
to the way you would set an expression breakpoint anyway.
You use the dash O parameter, which specifies the type name
of the error you want to catch.
Now if you do that, you will stop only when the type
of error you're interested in would be thrown.
Now, finally, of course you can still catch errors the way you
would in normal code.
After all, the REPL is meant partially as a way to learn
and explore the way the language works.
If you import Foundation to get an NSError
and then you write some code that throws an NSError,
you can catch that NSError and print it.
If you do that, the output will be exactly the same
as if you had caught it in your own program.
So I hope you go out of this remembering two things.
First of all, add import your modules, and second of all,
try playing around with error handing in LLDB.
It's a great place to do it.
Now, for much more detail, not about how
to tell your program what to do,
but how to print the information your program produces
after it's done it, I would like to call
up Enrico Granata [applause].
ENRICO GRANATA: Hi!
I was in the Labs this morning.
And we were trying to look through a problem and one thing
that came up is, why can I not see this variable?
What's up with that?
And in order to help us figure that out,
we tried several commands.
We tried expression, we tried PO, we tried frame variable.
And people usually at this point ask, why do you have
so many commands to do pretty much the same thing?
Look at my data, see what is happening in my program.
Well, all of you here get the insider scoop now.
Let's look at the commands that LLDB has
to let you look at data.
There's three of them.
Frame variable, the expression command, which is the p command,
the expression dash uppercase O,
which as you've been told before,
you know as the po command.
First one, frame variable command.
The frame variable command which you can shorten as frv
when you type it, is pretty much the Xcode variables view.
It lets you look at all your local variables.
It lets you look at only a few of your local variables,
and optionally you can also apply formatting
with the dash dash format flag.
One thing I want to highlight, because I will get back to it,
is when you see that Tuple in the first output,
that is an aggregate, that is an object
that contains other objects.
The things that are within that aggregate,
we call them children.
The expression command is the command you've seen a lot
in this session.
I'll be really brief.
Of course, with the expression command,
you can do simple arithmetic, as you'd guess.
It is totally possible to use previous results
and actually do more stuff with them.
And of course, the expression command also knows how
to do custom formatting of your results.
One thing I want to point out, once again, children.
Third command, the po command.
This is probably the command that all of you
who are Objective-C developers, I'm guessing quite a few prior
to Objective-C code, and you know the po command.
You can create objects and get their description printed out.
You can just create an NSArray or print the existing NSArray
and you'll see the contents of it.
Or, guess what, it simply works for a string.
So, three commands.
They don't all do exactly the same thing,
as you probably guessed by now.
Actually, they are somewhat similar
and not at all identical.
For instance, the expression command
and the po command are 'run my code' commands.
Whatever code you type, these commands will run it.
But then the frame variable command
and expression command do step 2 differently.
When they have to show you your result,
they use the LLDB formatter system.
We talked quite a bit about the LLDB formatter system
in past WWDC sessions.
And you should totally go check them out.
But really briefly, LLDB has knowledge
of some built-in system types and automatically formats them.
NSArray, NSDictionaries with strings.
And it is possible for you
to provide your own format as written in Python.
On the other hand,
the po command will not use LLDB formatters.
The po command runs some more extra code behind the covers,
behind the scenes, to actually produce the result it has
to show you.
You probably have written at least one description method
in one of your Objective-C classes and then you realized,
oh, that's what po prints.
Now I want you to think for a second about these two models
of actually taking your object and producing data for it.
The LLDB formatter's model is what we call the 'out
of process' formatting model.
Why? Because the formatter sits outside of your process.
It is either built knowledge into the debugger
or you brought some Python script to represent your object.
It's different languages, different files
that live in different scopes.
On the other hand, that external formatter lives in the dugger.
It is really easy for that guy to get access
to all the knowledge about your program that the debugger has.
It's kind of like a bird's eye view of your process.
Because of that, it's also really easy for this kind
of formatter to make sure
that your program state is not changed.
You don't want to change the state of your program
as a result of looking at data in the debugger.
The debugger is kind of like the stage inspector.
It looks at things and tries not to change them.
The other model, the po model,
the 'write a description method' model is an in-process
You write your data and your formatter together,
you write them in the same language.
You probably even write them in the same file.
And because your formatter is just code that runs
in your application, it gets easy and full access
to the object model of your application.
But with great power, comes great responsibility.
You want to make sure that your formatter does not change the
state of your program.
You want to make sure you don't do anything in your formatter
that will alter the object you're trying to represent.
Well, you say, okay, Objective-C has an in-process
I can write a description method,
the debugger will pick it up.
What about Swift?
Turns out, and once again, those of you that are
in this room get the insider scoop,
that Swift has had an in-process formatting model
since the very beginning.
But, where is it?
How do I use it?
I hope all of you have used the Swift playground.
If you have used the Swift playground,
you have used the Swift in-process formatting model.
It has been there since the beginning.
So what is new?
Now in Xcode 7, we take that very same model,
we make it public API.
It is available for you to use.
And it still powers the playgrounds,
but now it also powers the LLDB po command.
Now you've got the right Swift formatters
for your Swift objects.
How? Let's look a little bit under the cover.
The model is based upon four protocols.
These are their names.
Yes, I said four protocols.
And their names are also quite lengthy.
But I wouldn't worry too much about that.
It's possible to only opt-in partially to this model.
The four protocols doesn't mean you have
to conform to all of them.
You can choose a subset to get the result you want done.
ENRICO: Conform to those, do that work, and profit.
Let's look at the protocols.
CustomStringConvertible is the protocol that tells,
that tells us, how would I print my object as a string?
It doesn't only tell LLDB.
It also tells Swift.
How? The Swift print function,
as well as the Swift string interpolation feature,
both use the CustomStringConvertible
That's a lot of benefit.
How hard can it be to get it?
It's pretty easy.
I have a data structure that represents the bottles
of beer song, because I'm preparing myself for the bash.
And I want to print the lyrics.
I create an instance of that, and I say, how many bottles
of beer are on the wall.
But that's for when everything in my app works fine.
If I'm debugging it, maybe I need a little bit
That's where Custom Debug String Convertible kicks in.
It's a debugger specific representation of the object.
What debugger-specific means is really up to your app.
It's really up to the semantics of your object model.
But, as a hint, the debugPrint function will default
to choosing this protocol.
Of course, both print and debugPrint will fall back
to the other conformance if their favorite one isn't there.
How to get this to work?
Let's extend our bottles of beer.
Because we are debugging, we want to know a little bit more
about the bottles of beer on the wall, so we can just check
that they are all correctly stout, and it is going
to be a great bash because they all are.
Third protocol is Custom Playground Quick Lookable.
As the name implies,
this protocol is specifically for playgrounds.
It is meant to provide rich graphical representations
of your object in a playground.
You want an example?
I can write a data structure that represents a person,
and then I can get a depiction of a person to show
up in my playground sidebar as a result
of creating an object of that type.
I'm quite sorry to disappoint you guys, I really wanted
to get this to happen.
But, unfortunately the t-shirts
with my person will not be sold at the conference.
Sorry. But I have something to make up for that.
The last protocol in the lot, the Custom Reflectable protocol.
It allows us to invent a entirely custom
Yes, I said I would get back to the word 'children' and I did.
When I say an entirely custom children hierarchy,
what I mean is that I can make a new --
I can make a new structure for my object.
I can tell the language, I can tell the debugger,
I can tell the playgrounds,
this is what my object is actually made of.
This is how you should see it.
And the way you do this, the currency that you transact in,
when you're trying to describe the structure of objects
to Swift, is called a Mirror, reflectable mirror.
Let's see an example, without further ado.
I have an application whose job it is
to collect temperature samples.
It has got a couple data structures.
One that describes a moment in time, and one says,
for a given moment in time,
this was the temperature information I got.
And then we have got temperature samples, of course.
Now I'm debugging this app.
I'm trying to see what is going on,
how it's processing the samples.
So I say po temperatures.
And that's what I got.
Honestly, you know, I look at it and I say,
it is not bad for a default.
But I immediately see a couple things I want
to change about this.
Why stack time on two lines?
I really wish it was all in one line.
And it would be great if it was in a.m.
/p.m. format, too.
And that temperature, I look at it and I'm like,
what scale is that in?
I don't even know.
Is it Kelvin, is it Reaumur, is it Rankine?
We are in America and we want our Fahrenheit
ENRICO GRANATA: Well, the good news is that we can fix all
of these things, in two steps.
Let's go. Step one, let's get the time printed on one line.
Here is what I did to get that to happen for me.
I used an NSDate formatter.
Because I got to run within my application,
because the code I'm running
to format this object is actually just normal Swift code,
it is just code I would normally write as part of my app,
whatever framework, whatever library,
whatever technology my app would normally use
to get this job done, I can just use it in my formatters.
In this case NSDate formatter.
Step two, let's get those Fahrenheit degrees going.
How do we do it?
We create a mirror.
Here we are.
Now we are saying that our temperature data object is
structured as a container of three things.
A time, a temperature in Centigrade,
and a temperature in Fahrenheit.
The time is the string interpolation
of the actual time data stored inside the object.
And because it is string interpolated
and because we provided a custom string convertible conformance,
that will be automatically picked up.
One more thing worth noticing here is
that when I get both scales, when I get both Centigrade
and Fahrenheit for my result printing,
I don't change the value stored inside the object.
Of course, you say, you don't.
Well, it is actually quite a bit important to remember not
to change the state of your program
by writing in-process formatters.
And now that we've done all this work, now we've got all this,
now how do we benefit?
Well, we could try po-ing that again.
And here we go.
And now I look at it again.
I see my object.
And I can look and I see now
that at 6:30 p.m.it was 93.2 degrees.
Yup, in case you are wondering, it was really hot
in Cupertino yesterday.
And now we did this work but we did it because we were smart
and we did the work ahead of time.
Sometimes I'm trying to debug something
and I got my program just where I want it.
I have got this one hard-to-reproduce bug
to finally happen.
But now it is really hard to look at the data.
The data is confusing, it's complicated.
And I wish I had done this work before,
so that now I could actually look
at my data in much simpler ways.
But alas I haven't.
All hope is lost.
Nope! No, it isn't!
You can add conformances and runtime, too.
Through the expression parser you can add these conformances
and run while debugging your app.
On the other hand, you cannot change them.
Existing conformances stay.
You are in the REPL, you're experimenting.
You really wish you could po something.
Oh, how I wish I could add a conformance.
You can do that in the REPL as well.
And, of course, but I'm sure you all expect that, guess what?
In playgrounds, too.
Look at that!
It seems to me that a lot of this work that we've done
over the last year in debugger land that you've heard
from Kate, from Sean, and from me, has to do with making sure
that it is really easy to access as much of your information
as possible while you are debugging.
Access to the Objective-C runtime gives you more insight
into fields that before were unavailable to you.
The SDK modules offer an unprecedented level of access
to the operating environment
that your application is running in, more types,
more functions, even macros.
And in-process formatting,
in-process formatting is a great way for you
to create compelling representations of your types,
that apply across the board.
They apply in the playgrounds, they apply in the REPL,
they apply in the debugger.
For more information, of course, do not hesitate
to visit our website, Swift language docs,
or Developer Forums, of course, the Labs, and Stefan,
our Evangelist, is but an email away.
Thank you very much, and have a great WWDC!
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.