Transcript
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
[ Music ]
[ Applause ]
>> Thank you.
Welcome. So, I'm Doug Gregor.
I'm here with my
colleague Michael Ilseman
to talk about design.
Because good design
makes us more productive.
And good API design helps
us write code that is clear,
concise and beautiful in Swift.
And that's every bit as true
whether you're writing your API
for a million other
developers or just
for yourself inside
your own app.
Because good design
really does matter.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So, we're going to be talking
about the Swift API
Design Guidelines today.
These are new guidelines we're
introducing with Swift 3.
We're going to talk about
the philosophy behind
these guidelines.
Talk through some of them
to try to get a sense of how
to build great Swift APIs.
Now, then we're going to talk
about the Grand Renaming.
Which is what we like
to call the application
of these guidelines to all of
the APIs you use day to day.
Swift Standard Library.
Cocoa and Cocoa Touch APIs.
Now, this Grand Renaming
is going
to affect a lot of your code.
Pretty much all the
code that you've written
in Swift 2 is going to change
in some sense in Swift 3.
>> Whooh!
>> Yes!
[ Applause ]
So, we're going to talk through
what that means for your code.
And how to deal with it.
And starting thinking
in Swift 3.
Finally, we're going to talk
about some new tools and tricks
that we have for mapping
C and Objective-C APIs
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that we have for mapping
C and Objective-C APIs
into beautiful Swift APIs.
That's a whole lot
of what and how.
The biggest question
you probably have
about the guidelines is why?
Why, after two years of working
with Swift are we changing
all the APIs that you work
with on a daily basis?
And the answer comes down to
the character of the language.
Because every programing
language has its own
distinctive character.
It has its own syntactic feel.
But it's more than the syntax.
It's also the kinds of tradeoffs
that that language
decides to make.
Does it skew toward
safety, performance?
Do you care about some
mathematical terseness
or something that is
clear and easy to read?
You take a look at Swift code,
and it's instantly recognizable.
And you can see Swift
rendering an opinion
about certain things here.
It uses trailing closures.
So the control flow works
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So the control flow works
with your libraries
and your APIs nicely.
It has optionals, so you have
to consider the possibility
of nil everywhere.
You can't just ignore that
this possibility exists.
And so, when you're working
with Swift it feels like Swift.
But it's not just the
language contributing to it.
It's really the APIs you
use day in and day out.
And these APIs have to meld with
the character of the language.
So you get a consistent,
whole experience of working
with beautiful Swift code.
Now, these are some Cocoa
and Cocoa Touch APIs.
And these APIs were
designed based
on the coding guidelines
for Cocoa.
You may have read these before.
I hope you have.
They espouse clarity
and consistency
in the design of APIs.
And we've been applying these to
thousands upon thousands of APIs
over more than a decade, all
right, to produce the Cocoa
and Cocoa Touch platforms.
And these wonderful APIs we use.
Now, these guidelines
were designed
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now, these guidelines
were designed
for a different language
with a different character.
And so, when you bring all of
these APIs that were written
for Objective-C into
Swift unmodified,
they seemed a little
bit out of character.
They feel a bit not-Swifty.
What's that even mean?
Not Swifty?
You hear it a lot.
But in essence, this is why
two years in to having Swift
as a platform that
many thousands
of developers are using, is
the right time to reevaluate.
Because we have experience
from a much larger community
to understand what works
well in Swift code.
And so we set off to design the
API Design Guidelines to try
to codify what it is
to build a Swifty API
and help everyone build more
consistent, more clear APIs
within this language
that we love.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now, in the lower
left you see SE-0023.
That's the Swift
evolution proposal number
that contains all of the
API Design Guidelines.
You'll see these proposal
numbers throughout the talk.
Again, you can go
look at Swift.org,
look up this proposal number
to see more information
about that particular change
to the Swift language.
But for now, let's talk about
the guidelines themselves.
The main principles of the
Swift API design guidelines are
clarity at the point of use.
Where your API shows up
in someone else's code.
And we love concise code.
But the clarity is the
most important aspect.
It's more important
than having brief code.
That said, concise code
does happen in Swift.
Swift code does tend
to be more concise.
We can feel it when we
look at the language.
And we see it in actual
metrics when we talk about apps
that have been ported into
Swift and written in Swift.
But that comes from using
the right contextual cues.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
But that comes from using
the right contextual cues.
Let's dive into these
principles a little bit more
and look at some APIs.
So, we're going to
talk about usage first.
So why focus on the use sites?
Well, for one, it's just
a simple numbers game.
You're going to write
your API once.
People will look at that
API in code, or maybe look
at the documentation for
it a couple of times.
But the vast majority of
times that your API matters,
the number of times it's seen,
it's going to be in the context
of a whole lot of other code.
And when you're in that
context, you have all
of this extra rich
contextual information.
You have the local variables,
their names, their types.
You have uses of related APIs.
And so the goal of
your API isn't
to stand up and say, "Hello!
I'm here and I have
a nice big name."
The goal of your API is to fit
in with the rest of the code
so that the end result
is beautiful.
Now, when you're focusing on
use cases, resist the temptation
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to optimize for bad code.
It is absolutely true that
someone can go and use names
like A and B and C
for all the variables.
And this code is
not clear anymore.
You can't fix this bad
code with your API alone.
All you can do is make other
code more verbose or less clear.
So focus on proper use cases.
Focus on the beautiful code
and tune your API for that.
Let's actually look at
a specific example here.
And so we're going
to start with an API
that is removing an
item from a collection.
We're going to start by
calling it removeItem.
Sounds good.
But I've made my first mistake.
This is not a use case.
This is just a name
in the abstract.
So, let's bring up a use case
that I have in mind today.
Don't ask why.
He knows. So, remove item
ted from the list of friends.
Now, you'll notice that
there's actually two things
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now, you'll notice that
there's actually two things
in this use case
that are referring
to what the argument is.
They're trying to
describe the argument.
There's the word item
that's part of our name.
And then there's
the argument ted
which is some local
variable with a type.
Of these two, the variable ted
is the actual better descriptor
of what the argument is.
It's in context.
As you're reading this call
in context, you know what
that local variable is.
It means something.
It has a strong type.
So maybe the problem
here is really
that the word item isn't
sufficiently descriptive.
Maybe we'd feel a little
bit more comfortable
if it were called removeObject.
Well, that's probably not right,
because we're probably using
value types here anyway.
So it's just actively wrong.
We could say, hmm,
removeElement.
Okay. Collections in Swift
use the term element.
But, it's not helping us any.
And moreover, if we
were to start writing
down different use cases,
it might actually
become less clear.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Here I want to remove
element caffeine
from a list of organic
compounds.
That's misleading.
It's wrong.
Caffeine is not an element.
It's a compound made
up of elements.
So, our attempt at using
some innocuous word here
to describe the argument
has actually led us
to less clear use cases.
Okay. So maybe the problem
is that we should stop trying
to get these innocuous sort
of general words in there
and we should be
really specific.
Remove person ted from
the list of friends.
It's a little awkward
in English.
I wouldn't say that.
I would just say, remove(ted).
But moreover, if I do this, if
I try to get really specific
for this generic API,
well now I expect
to be specific everywhere.
And now there's one conceptual
API of just removing an item
from a collection has
different names throughout the
source base.
That makes it harder
to understand.
It's harder to recognize
that actually we're talking
about the same thing
in different contexts.
So, the Swift API Design
Guidelines go a different route.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So, the Swift API Design
Guidelines go a different route.
Omit needless words.
If a word is not
contributing to the clarity
of the use site,
don't put it in.
So we can just say, remove(ted)
from the list of friends.
It reads really nicely.
Now, one of the reasons this
works so beautifully in Swift is
that it has a strong static
type system to make sure
that you don't write
nonsensical code and again,
interpret it in the wrong way.
And so the strong static
type system is making sure
that the argument you passed
to remove is an element
of the corresponding collection.
If you were to do
something truly inadvisable,
like for example, try to
remove caffeine from your list
of friends, you're going
to get an error message
from the compiler telling you
that that doesn't make sense.
So, we've talked about
readability of the use sites.
And we've eliminated a bunch
of words, which might put you
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And we've eliminated a bunch
of words, which might put you
in the mind that what we're
looking for is terse code.
That's not true.
We want clear code.
But, clear code has to hit
the correct point in this sort
of spectrum of terseness
to verbosity.
And if you think about what is
verbose code, verbose code is
when you have extra information
in there that you don't need.
And verbose code
actually hurts clarity.
Because what is your mind
doing as you're reading
through this verbose code?
It's trying to filter out
all of this extra noise,
all the things you don't need
because they're redundant,
to find the actual
signal in there.
Now, the other end of the
spectrum is also not good.
If you have code
that is too terse,
then you're missing critical
information that you need
to understand that code.
A sign that this is happening,
that you've made
things too terse,
is when you're reading code
and you find yourself jumping
to the API documentation
all the time
because the APIs themselves
are insufficiently descriptive.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
because the APIs themselves
are insufficiently descriptive.
So we're looking for this
sort sweet spot in the middle
where you get clear
code that is concise.
So all of the information you
need to understand those APIs
and how they work is there in
the contextual information.
This is actually a principle
of the Swift language itself.
So I'm going to put up
a bunch of code here.
And in this bunch of code we
actually have verbosity that's
not needed in Swift.
That verbosity is all of these
explicit type annotations.
These aren't adding
to readability.
You can get a sense of
what the types are just
by reading the APIs.
And indeed, in Swift you
probably wouldn't write the code
this way.
You would probably align
that type information
and let the static type
trebicore [phonetic] do it
for you leading to
more concise code
that you can still read
through just as well.
Now of course, types
are important.
And the types are there.
And if you need reassurance
about what a particular type is,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And if you need reassurance
about what a particular type is,
if it'd help your
understanding, of course,
the answer is just an option
click away inside Xcode.
So let's look at another
API and try to talk
about when a word
actually is needed
to help describe an argument.
We'll take this little API
here that adds a child view
at some particular point
within some main view.
And so we write out a use case.
Always start with the use case.
And think about the words
that apply to the arguments.
So in the first case we have
the word child is applying
to this view argument that
is our first argument.
Is child adding something?
Well, we know from
the static type system
that sidebar is going to
be a view of some sort.
But this word child
is clarifying the role
of this parameter
in the operation.
It's stating that this argument
here will become a child.
It's establishing hierarchy.
That's really important
information
for understanding
what this API does.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
for understanding
what this API does.
Now, in the second argument, we
have atPoint and then origin.
What's origin going to be?
Well, it's going
to be a CGPoint.
This API is going
to take CGPoints.
And you can't provide anything
other than a CGPoint here.
So this word, it's
not adding anything.
It's just restating
information that's already
in the strong static type
system that will be enforced
by the Swift language.
So take it away.
You don't need it.
And now notice what the
call site looks like here.
If we read it out, you add the
child sidebar at the origin.
It reads grammatically.
All right?
This is one of the principles
of this particular API
Design Guidelines is
that we really want the use
sites to read grammatically.
And we had this for our
example here of remove(ted)
from our list of friends.
Now, let's look at
a very related API.
So this is removing
specifically some element.
If we look at, say, the
API that removed something
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
If we look at, say, the
API that removed something
in a particular position.
Read it out.
Remove position of former
friend from the collection.
It's not right.
This isn't the collection
of positions.
This is a collection of people.
To actually make this read
well we want to say, remove at.
Let's read this out remove at
the position of former friend.
Notice how we've clarified the
behavior of the API by putting
in this first argument label
to describe the relationship
of the argument to the method.
This brings us to naming
and the idea of naming.
So, in Swift, a function
name is comprised
of so-called base name,
which here is remove in both
of these APIs, as well as all
of the argument labels
for the arguments.
And so these two related APIs,
they share the same base name
of remove because they're in
this method family of operations
that remove something
from a collection.
But their argument
labels are different
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
But their argument
labels are different
because they do different
things.
One removes an element
by its identity.
The other one removes
its element based
on its position in the sequence.
So slightly different
APIs have different names.
Now, you can overload
based on type information.
And if two APIs that share
the same compound name.
But you should only do this
when the APIs have
the same semantics.
So it's fine to overload
the append name
with no argument label
here to append a character
or a string to some text.
Because fundamentally these
are the same operation.
They're just overloaded on
different types for convenience.
Now, when you're coming up with
first argument labels, again,
you want that use case
to read grammatically.
And that includes the base
name of the method as well
as the argument label.
So my first API here
removeBoxes (withLabel:WWDC).
Notice how that reads well.
Now, inside that API we
have a prepositional phrase.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now, inside that API we
have a prepositional phrase.
So it's a little bit of
English grammar here.
But essentially it's withLabel.
And it describes the
relationship of the argument
to the actual operation
of removing boxes.
And so when you have one of
these prepositional phrases,
put it on the first
argument label
to describe the first argument.
You also use first
argument labels
if you essentially can't
form a grammatical phrase
because it would be misleading
to have the first
argument in there.
And so here we have
a viewController.
And we say dismiss(true).
What is that?
I can't banish a Boolean
constant to anywhere.
And so to make this
read grammatically,
I need to put an
argument label in there.
Dismiss the viewController.
And it's animated.
Animated is true.
And so this is extra
information that's coming along.
The first argument label breaks
it up so that it reads well
and it's clear that what
we're dismissing is the actual
view controller.
There are a couple of
other rules you can read
about on Swift.org.
But essentially you still will
omit first argument labels
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
But essentially you still will
omit first argument labels
in some cases.
And these are cases where
in the API it reads well
to just have the argument
there, insert michael
at the start index of friends.
That reads well.
We don't need a first argument
label to make it read well,
and so we leave it out.
Okay. Let's talk
a little bit more
about naming before we move on.
So, when you name a method, name
it based on its side effects.
So, use English verbs, commands,
to tell the receiver
do something.
So here we might say, okay, we
have the friends collection.
Reverse it.
A viewController, present it.
Organic compounds, append to it.
Right? These are actions taken.
We name based on
the actions taken.
Now, when we have methods whose
primary responsibility is just
to return some value
we use a noun.
Describe the thing
that is being returned.
And so here we can ask for the
background title of a button
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And so here we can ask for the
background title of a button
or the suffix of
this friends array.
Now, when you're
dealing with value types,
it's sometimes the case that
you have both a mutating
and a non-mutating form of
essentially the same operation.
And here what we like to call --
we apply what we like
to call the ed/ing rule.
And so this follows
from English grammar.
And essentially you
start with the verb form.
And so here we have
reverse a collection.
You're commanding X
to reverse itself.
Now, for the more
noun-like other form,
we use the "ed" rule.
So we ask for X reversed.
All right?
Where we're describing what the
result is that we want to get.
And that corresponds
to the mutating form.
Now, when the "ed"
rule doesn't apply,
the "ing" rule generally does.
This is usually when we
have an argument here.
So we have a document directory.
We can append a path
component to it.
That's mutating.
We told it to append this.
Now we have the non-mutating
form.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now we have the non-mutating
form.
Give me the document directory
appending this particular
path component.
So these rules, and many, many
more, are described on Swift.org
as part of the API Design
Guidelines document.
Highly recommend that
you read them and try
to apply them to your own APIs.
But of course, these guidelines,
they're not interesting
unless they actually are
widely applied.
And this is why we set
off on the Grand Renaming.
Which is the application
of these guidelines
to the Swift Standard
Library, to the Cocoa
and Cocoa Touch APIs as well as
targeted improvements to APIs
like Core Graphics,
Grand Central Dispatch.
They're used all the time to
give them these Swifty makeovers
to be beautiful in Swift.
I talked a little bit about the
scale of the Grand Renaming.
So, this is a little screen shot
of a small Swift app called
Lister we've been shipping
for a couple years.
It's one of the sample
applications
when it's being migrated
from Swift 2 to Swift 3.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
when it's being migrated
from Swift 2 to Swift 3.
You can notice there's a whole
lot of .swift files on the left.
Basically every Swift file in
the entire project is changed
by the transition from
Swift 2 to Swift 3.
And if you're looking at some
of the details in the middle
and the right panes here,
you probably noticed that all
of these Cocoa API
names have changed.
Okay? So, there's a
lot of change here.
We'll talk about that.
But really what's interesting is
that these Cocoa APIs
you may have been using
for a very long time,
the APIs are the same,
but now a given API has
two different names.
Has one name that's
appropriate for Objective-C
and one name that's
appropriate for Swift.
As a Swift programmer, most of
the time you don't need to care.
You can entirely work
within the Swift names.
Use your generated
interfaces, the documentation,
everything will show
you the Swift names.
And that's all you'll deal with.
However, there are times
when you're interacting
with the system where
you actually do need the
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
with the system where
you actually do need the
Objective-C name.
For example, if you're
wiring up target action.
And so, here we actually need
to provide a selector
to target action.
And we have this string literal.
What do we write here?
I don't know.
You can look at the
generated interface.
Maybe ask your Swift compiler
friends on Twitter or something.
You can come up with the answer.
That's fine.
But, please don't do this.
Because this is a
really, really weak link
between this string literal,
which is super easy to mistype,
and the method above that
you actually meant to call.
This is why in Swift 2.2
we introduced #selector.
#selector is a very
simple thing.
It's an expression.
It takes in the name
of a Swift method
and produces the Objective-C
name for that method.
You don't have to care what
that Objective-C name is.
The compiler will get it right.
And the great thing
about this, of course,
is that Swift makes sure
that that method exists.
Makes sure it's exposed
to Objective-C,
and computes the correct name.
And of course, this is
safe against refactoring.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And of course, this is
safe against refactoring.
Works with code completion.
So it's a much nicer
development experience.
That means you don't
have to worry
about the Objective-C names.
In Swift 3 we've extended this
so you can also refer to getters
and setters of properties.
So we round out the set
of Objective-C method names
you can actually compute.
So this is very easy to use.
You just pass in the argument
label setter or getter
to get the setter or
the getter respectively.
And then refer to an
Objective-C property.
And of course, the
compiler will validate
that that property exists, that
it's exposed to Objective-C
and get the right
Objective-C name for it.
Now, selectors aren't
the only sort
of stringly type thing
we have when talking
about Objective-C method names.
We also have Key Paths,
which are notoriously hard
to get right when
writing these things
as string literals
with no validation.
And so, in Swift 3 we're
also introducing #keyPath.
[ Applause ]
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
[ Applause ]
#keyPath does exactly
what you'd expect.
You get to refer to a dotted
sequence of property accesses.
Compiler validates that these
are actually Objective-C
properties, gets
the right names.
And then produces the
string that we pass
down into the frameworks.
And so between #selector
and #keyPath,
you can essentially not worry
about the Objective-C names.
You just write everything
in terms of the Swift names
and you stay in that
set of names.
You don't have to
straddle the boundary.
Now, there are cases
where you do need to think
about what the Objective-C
names are.
You may have a mixed project
with Objective-C code in it
that needs to refer to the
names in your Swift code.
And names like this
handleDragWithSender
for just don't feel right
when they're in Objective-C.
So for these cases, you can
use the @objc attribute.
And in parentheses put the exact
Objective-C name that you want.
And that will be reflected in
your generated headers and all
of the metadata and everything
else so you get specific control
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
of the metadata and everything
else so you get specific control
over the Objective-C
names, but no other part
of your Swift code actually
has to care about this.
And of course, this @objc with
a name works for properties,
it works for methods,
classes, protocols.
Anything that can be exposed
to Objective-C from Swift,
you can control the name here.
So you get nice Objective-C
APIs for your Swift code.
Okay. There's a lot of
change coming with Swift 3.
The Swift language itself
abstracts away the need to worry
about the Objective-C names,
and yet it gives you the
control you need for those times
when you do care about
the Objective-C names.
The tools are here to help you.
So the Swift 3 migrator is going
to Swift 2 code and migrate it
to the Swift 3 names
and the Swift 3 syntax.
It's a lot of change.
But these tools will help
you get over the hump
and get working with Swift 3.
Now, the Swift 3
migrator is a great tool.
But it can't migrate
your muscle memory.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
But it can't migrate
your muscle memory.
Certainly can't migrate
all that code you copy
and paste from stack overflow.
So, the Swift compiler's
also here to help you.
The Swift compiler, it knows
the Swift 2 names of all
of these APIs as well
as the Swift 3 names.
So if you write or paste
in some code from Swift 2,
it'll recognize the old API
names and give you diagnostics
with Fix-its to update your code
so you can get moving faster.
Additionally, we've put
in near miss detection
when you're implementing
optional protocol methods.
And so this is great when
you're implementing a delegate.
You make some minor
mistake in the name
of that delegate method
you want to implement.
Now you'll get a warning with
a Fix-it to fix up the names
so you can be sure that your
method will be called properly.
All right.
Now I'd like to turn things
over to my colleague Michael,
who's going to talk
about the mapping of C
and Objective-C APIs into Swift.
[ Applause ]
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
[ Applause ]
>> So that's a lot about Swift.
But what if you're an
Objective-C developer
or are working with
a mixed project?
All of your Objective-C
APIs are available in Swift.
They always have been.
But as Doug explained,
they were designed
for a different language.
They are increasingly starting
to feel a little bit
foreign in Swift.
So today I'm going to show
you how you can take control
of the situation and give
your Swift users the APIs
they deserve.
I'm going to start with
a couple Objective-C APIs
as they were imported
into Swift 2.
Here we have two methods;
saveToURL, forSaveOperation
and revertToContentsOfURL.
But these don't really express
the API Design Guidelines
that Doug outlined.
There's a lot of
redundant type information.
We're not making effective use
of the first parameter labels,
first argument labels.
So I'm going to start with what
you get for free automatically
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So I'm going to start with what
you get for free automatically
in the Swift 3 compiler.
Swift 3 has improvements to how
Objective-C APIs are imported.
The Swift compiler will
inspect method names
and use grammatical
cues in order
to infer first parameter labels.
The Swift compiler will
inspect names in order
to eliminate redundant
type information.
The compiler can even
infer default arguments
for common Objective-C idioms
such as completion handlers.
Or option sets.
And also, there are
new value types
such as URL that
bridge to NSURL.
So when we import, we
just import it directly.
To find out more about
these value types, visit,
What's New in Foundation
for Swift later today.
So, automatic inference
is great and all,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So, automatic inference
is great and all,
but fundamentally it's
driven by heuristics.
The compiler, it
can't read your mind.
It doesn't know your intent.
And every now and then you
want to specify your own name.
So we've extended NS Swift Name.
Now, NS Swift Name has
been around since Swift 2.
But in Swift 3 we support
full compound naming.
A compound name is a base
name plus the argument labels.
So in this case, we
have two methods.
They're performing semantically
very similar operations.
But they differ basically in
how they treat their argument.
And so we import them with the
same base name constraints,
but we say we want it equal
to this anchor or greater than
or equal to this anchor.
Now, method names will get you
a long way towards a Swifty API,
but this doesn't
quite go far enough.
Here I have some simple code
that's creating a standard
Gregorian calendar.
But if you look at this, this
doesn't really feel very Swifty,
especially
NSCalendarIdentifierGregorian,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
which is a plain
string global variable.
Plain string global variables,
that's not really
how we do Swift.
This is what we jokingly refer
to as stringly-typed API.
And the fact that this
API takes a string,
that kind of allows
simple errors.
And yes, I know this
bug is shallow
and a developer would
catch it right away.
But the fact that a user of this
API has to remember what it is
and it's not a valid
string to use here,
that's an unnecessary
cognitive burden.
So, why is this API like this?
Well, we all know why
this API is like this.
It came from Objective-C.
But the fact that this
API came from Objective-C,
that's an implementation detail.
And that implementation
detail is leaking out.
And at Apple, we're
not big fans of leaks.
To figure out what's
going wrong,
let's see how this Objective-C
API is mapping into Swift.
Our global variable comes in as
a global variable, of course.
But other than the name
of this global variable,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
But other than the name
of this global variable,
nothing really tells you
that this a very specific string
meant for a very specific API.
Now, we could try
adding a typedef to try
and hint at our intent.
But a typedef, it's
just a type alias.
It's a new name for an old type.
And so that doesn't really
fix the problem here.
So, how will this API
look if we were going
to design this fresh in Swift 3?
Well, we'd probably make a
new wrapper type around string
to get some strong typing.
And these global
variables, well,
those would instead
be static properties.
So in Swift 3 we introduced
a new attribute just
for this use case.
You can access this attribute
through NS Extensible
String Enum when you wish
to tell the Swift compiler
to make a new wrapper
type around your string.
You can just add
this to your typedef.
And the importer will create
you the new type around this.
And any global variables of
this type will get imported
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And any global variables of
this type will get imported
automatically as static
properties of this type.
Now, we chose a struct here
because this is extensible.
That is, other modules may
want to define their own.
And if they do, they will also
be imported as static properties
on an extension of this struct.
Now, behind the scenes the Swift
compiler will map this directly
to the underlying stored value,
meaning that there's no
extra overhead or boxing
or intermediaries involved.
So, let's focus on the use site.
Because good API design is
always focused on the use site.
Before and after.
And because the type
context is clearer,
we can even just say .gregorian.
[ Applause ]
I'll kick it off by
starting myself next time.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
I'll kick it off by
starting myself next time.
Also, in Swift 3 NSCalendar,
it's now just known as Calendar.
This is a little fix up.
So, we have method
names and we have types.
What else do we have?
Well, we have an elephant.
That is, we have an
elephant in the room.
And we've been pretending
not to notice it.
But it's always been there.
And it's C.
Now, with Objective-C, the APIs
were already object-oriented,
so when we import them we
just change a few strings,
add a few types.
It's relatively straightforward.
But what about C?
I'm going to focus
on Core Graphics.
Now, Core Graphics is a
very popular API that's used
by pretty much every
Swift app out there.
It is a powerful API.
But it looks and
it feels like C.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
But it looks and
it feels like C.
Let's start with some code.
I have two functions here.
The first one transforms a --
takes a transform and rotates
it about a given offset.
And the second function
traces a path in red.
Now, neither of these
functions are too complicated,
and the details don't matter.
But I just want to look
at how does this look?
How does this feel?
Go ahead and take a moment.
So, if you notice, this
code is completely filled
with global variables
and global functions.
And we don't really like
global functions, of course.
Or global variables, of course.
So, how could we
take an API like this
and make it look Swifty?
How can we make it feel Swifty?
Well, for that, we revisit
our friend NS Swift Name.
You can use NS Swift
Name in order
to import globals
as members of types.
Let me start with
global variables
to show you what I mean.
Here at the top I have
this C definition.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Here at the top I have
this C definition.
Below that the generated
Swift interface.
And underneath that
the Swift use site.
And we will be focusing
on the Swift use site
and improving the
Swift use site.
So we add NS Swift Name and we
use typename.membername in order
to tell the Swift compiler that
kCGColorWhite should be imported
as the static property
white on CGColor.
And of course, the Swift use
site can now use the properly
nested property.
[ Applause ]
And of course, if the
type context is clear,
users can even omit the CGColor.
Now Core Graphics has a lot
of different global functions
to create all different kinds
of CG Affine Transforms.
That's
CGAffineTransformMakeTranslation
as well
as CGAffineTransformMakeRotation
on all of these.
But in Swift we prefer
Initializer.
So we use TypeName.Init and
provide argument labels in order
to tell the Swift compiler
that this should really
just be an initializer.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that this should really
just be an initializer.
And we use argument
labels in order
to clarify the roles
of the parameter.
You can also import
as an instance member.
Use the special argument
label self in order
to tell the Swift
compiler what argument
to plug the reference
to self into.
And so now CGContextFillPath is
now just the method fill path.
And of course, the
Swift use site,
it just calls it like a method.
[ Applause ]
And the compiler will take
what's on the left of the dot
and plug it into the appropriate
parameter position as denoted
by the special argument
label self.
We can get more complicated.
You can prefix a Swift name
with getter or setter in order
to tell the compiler
to import this function
as a computed property
getter or setter.
Here ArtistGetName,
ArtistSetName are now just
the getters and setters
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
ArtistSetName are now just
the getters and setters
for the computed
property Artist.
Let's focus in the use site
because good API design is
always focused on the use site.
Before, we would use
a global function
to remember the former
name of an artist
and a different global
function to set a new name.
But now, our users can use the
computed property directly.
In all cases, this is
a zero cost overhead.
That is, when the compiler
sees myArtist.Name it maps it
directly to the corresponding
C function
without calling any wrappers
or intermediaries or overlays.
You can also use NS Swift
Name in order to nest types.
Remember earlier when we created
a new calendar identifier type?
Well, you can use typeName.
-- well member name --
nest a type name in
order to nest a type.
So in this case we get
Calendar.Identifier.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now, we love the
new NS Swift Name.
We love it so much that we went
absolutely, completely bananas.
[ Laughter & Applause ]
Don't clap for this.
And we applied it
to over 600 APIs
in Core Graphics alone, 600.
Now you can clap.
[ Applause ]
That's a lot of bananas.
So, let's revisit
the code from before.
Before, our global variable,
which is painfully
global, is now a member.
And because the type
context is clearer,
we can even omit the type name.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
we can even omit the type name.
Our global functions
are now methods.
[ Applause ]
Actually, now that
I look at this,
I think this code
can even simpler.
We don't need that
extra variable result.
And so now this reads --
[ Applause ]
And so now this reads
nice, natural and Swifty.
We take a transform.
We translate it.
We rotate it.
And we translate it back.
Very straightforward.
Moving on.
CGColorCreateGenericRGB as
well as all of the other many,
many different ways
to create colors,
these are now initializers
with argument labels.
And of course --
hold your applause --
and of course CGContextAddPath,
ContextPath and all
of the other crazy redundant
stuff, they're now methods.
[ Applause ]
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
[ Applause ]
We hope you enjoy
these new APIs.
So, to summarize, first we
presented the new API Design
Guidelines in Swift.
Good API design is always
focused on the use site.
Second, we looked at
the Grand Renaming.
This is also known as, "Oh, no!
Everything's different."
But don't panic.
It's okay.
The names are better.
The code is clearer.
And you have the
tools to migrate.
And last, we looked at
the new functionality
in the Swift compiler so
that you can have your own
Great Renaming.
For more information, visit
the page for this site
at developer.apple.com and check
out Swift.org, the homepage
of the open source Swift
project where you can see all
of the Swift evolution.
I also showed you Core Graphics.
But Grand Central Dispatch has
had its own Great Renaming using
many of these same
techniques I presented today.
So, Friday, check out
concurrent programming
with Grand Central
Dispatch in Swift 3.
Friday.
[ Applause ]