WWDC2019 Session 402

Transcript

[ Music ]
[Applause]
>> Good Morning.
Welcome to What's New in Swift.
Today we're going to talk about
two important and exciting Swift
releases.
Swift 5, which was recently
released in March, and Swift 5.1
which is available now as a
developer preview in Xcode 11.
Combined together, these two
releases further unleash the
potential of Swift as a language
and technology that both Apple
and all of you can build upon.
And that comes in the form of
really powerhouse story with
Swift and APIs.
With a shared Swift runtime for
third-party apps now shipped in
Apple's operating system, and
binary frameworks written in
Swift that can now be deployed,
we see the appearance of marquee
Swift-only frameworks from
Apple.
Further, the integrated support
of the Swift Package Manager
within Xcode brings Swift
packages directly into the core
workflows for app development.
And the Swift language itself
has grown new affordances for
building beautiful and rich APIs
and capabilities for expressing
API evolution.
It's a really, really exciting
time for the language and in the
story.
So let's kick things off by
talking more about binary
frameworks, in particular, dig
into some of the core
ingredients that made that
happen.
And those ingredients are ABI
and module stability.
ABI stands for Application
Binary Interface and it's the
rules that governs the nuts and
bolts of how compiled code can
interact together at runtime.
So details like a function call.
How does it actually work?
How are the values in the
arguments passed off from caller
to callee?
What is the available metadata?
How is it laid out in memory?
All of these details are
necessary for compiled code to
interact together.
So to kind of illustrate this
idea, imagine you have a program
written in Swift.
It's an app, could be command
line tool, it doesn't really
matter.
It's just some executable.
And it uses a framework that is
also written in Swift, and all
of this, you know, compiled code
is running together within a
running process.
So the executable is using APIs
from the framework and they have
to be able to talk to each other
at runtime.
Well, in order for this to work,
they have to have a compatible
ABI, like these two, you know,
pieces are compiled
independently but that compiled
code needs to work together.
Before ABI stability, the only
guarantee we had is that these
would have a compatible ABI if
they were built with the same
compiler.
And this was the case because we
were really evolving the core
fundamentals of Swift, making
sure all the building blocks
that we wanted to have in place
to build upon in the future were
in the right place.
Well, in Swift 5, we've
crystallized those details and
Swift now has ABI stability,
meaning these two components no
longer need to be built with the
same compiler as long as they're
built with the Swift 5 compiler
or later.
The second important ingredient
is module stability and this is
a compiled time concept.
So if you take a Swift framework
using of all the APIs in that
framework, those are part of a
shared namespace called a
module.
And when the Swift compiler is
used to build that framework, it
produces a manifest of all the
APIs in that framework that can
be then consumed by clients of
that framework.
And that manifest is called a
Swift module file.
So if we return back to this
example, imagine where-- you
know, we're compiling the
program so we have, you know, a
source file, it references the
framework, and what happens is
the compiler goes and reads
Swift module file, gets the
available APIs.
But the details in this module
file are really rich and in many
ways very tightly coupled to the
compiler itself.
So we had the same problem as
with ABI stability.
The only thing that worked is
that these two pieces had to
compile with the same compiler.
On Swift 5.1, we've introduced a
new complementary manifest.
It's called a Swift module
interface file.
And it can be used by frameworks
to provide a stable interface
that clients can consume.
It's a-- If you, you know, crack
it open, it looks like Swift
source code.
So it's also built on the notion
of source stability that we've
had in Swift for quite some
time.
So with these two ingredients,
you get Swift frameworks that
can be deployed and shared with
others.
[Applause]
Now there's a lot of really
interesting details about ABI
stability, was something that
was in the works for quite some
time.
If you're interested in finding
out a lot more details and we're
going to talk about today,
swift.org is a great resource.
This is the homepage of Swift
open source project.
And there's a couple of really
great blog posts about ABI
stability.
Also, if you're interested in
putting binary frameworks to
use, you know, for your own use
like, you know, sharing with
others, there's a great talk
later this week called Binary
Frameworks in Swift.
And it talks about some of the
considerations you should have
when sharing frameworks with
others.
Now binary frameworks are just
one part of that API story,
another big piece are Swift
packages.
And with the integration of the
Swift Package Manager now into
Xcode, they're part of the core
workflows of building apps.
Two great talks this week about
both creating and adopting
packages within Xcode.
And so the combination of binary
frameworks and Swift packages
provides a really rich set of
options for sharing APIs with
others.
So let's shift gears and talk
about performance.
Swift language was built to be a
modern, safe, but very
performant programming language.
And there are some key
performance benefits that came
out of ABI stability.
One of them comes down to having
that shared Swift runtime for
apps in the OS, and this came
out in March and so for macOS,
iOS, tvOS, watchOS and now
iPadOS, as of these releases,
there's a shared Swift runtime
in US that, well, third-party
apps, first-party apps,
everything in the system uses.
So what-- how does this actually
work, when does this come into
play?
If your app is built with Swift
5 or later, it will always use
the shared runtime in the OS
when it is present.
However, you may still be
building your app to deploy back
to an earlier OS release that
doesn't have that runtime.
In that case, Xcode will
continue to bundle a copy of the
runtime in your app so it can
continue to run on those older
OS releases.
But, we will always prefer to
use a copy that's in the OS
itself, so that, you know, that
copy in the app will be inert
when running on newer system.
And as an optimization, the iOS
App Store will send out that
copy, the runtime from your app
when downloading into a device
that has the runtime in the OS.
So your users don't have to pay
that download cost.
So that's a really important
code size benefit, but the real
win about having that runtime in
the OS is now it can be
optimized as part of the
operating system itself.
And then those benefits can then
be imparted onto apps
themselves.
And one important benefit is
launch time.
So let's rewind back in time, a
year ago, we talked about Swift
4.2.
And if you take an Objective C
app, it does nothing, it just
starts up, does nothing at all
and a Swift app that's, you
know, similarly has no function,
it just starts up.
There's about a 5% overhead of
using Swift because of the work
needed to, you know, handle that
embedded runtime.
But when your app is recompiled
with Swift 5 compiler and runs
on an OS with that shared
runtime, that overhead
disappears.
[Applause]
This is really, really
important.
I mean, that latency is the
difference between users wanting
to-- you know, the moment they
want to start using your app,
and them starting experiencing
it.
Another important area that
we've optimized is further
tuning the committed, you know,
code from the compilers.
It reduces-- further reduce the
code size of Swift applications.
And this really has been like
fine tuning, like looking at
specific patterns in Swift
applications like chess, how our
dictionary literals like
represented, you know, when
their code generated and so
forth, and just trying to make
sure that, you know, the output
of the compiler is optimized for
those use cases.
And so with an assortment of
optimizations we see up to a 10%
reduction in code size when--
well, with the Swift 5.1
compiler.
And if you have optimized your
size, it's about a 15% reduction
code size.
So some pretty significant ones.
We've also continued to refine,
you know, the performance of
bridging.
And by bridging I'm talking
about the bridging between Swift
and Objective-C.
There's a really deep
inoperability between those two
languages.
So both Swift and Objective-C
have what we call common
currency types, that are used
throughout the API space.
Things like String and NSString,
Dictionary and NSDictionary.
And the inoperability between
those currency types is pretty
fundamental as part of the
inoperability between Swift and
Objective-C.
Objective-C APIs that use their
currency types are re-mapped
into Swift using Swift's
currency types.
Now this is a compiler-- this is
a combination of compiler work
but also there's a runtime
aspect when you pass a value of
one type off for another across
these API boundaries.
This is the part that we have
further attuned as part of Swift
being now part of the operating
system.
For example, bridging between
NSDictionary and Dictionary is
now 1.6 times faster.
And if you're passing a Swift
string off to Objective-C and it
bridges over as an NSString and
it's used, you know, from the
Objective-C side, its operations
can be up to 15 times faster.
[ Applause ]
And all these like little bit--
all these benefits really do add
up because these are types that
are used, you know, throughout
the API ecosystem.
Now speaking of strings, we
continue to refine their core
representation and we made a
major change to the string type
in Swift 5.
And this was an under-the-hood
change where we change the
unicode representation of
strings from UTF-16 to UTF-8.
Now this is completely
performance motivated.
There's a lot of rich detail
about this change.
If you're interested in finding
out a lot more than what I'm
about to say, swift.org, there's
a blog post that goes into the
actual technical changes and
what motivate it.
Well, I'll highlight a few
important things.
First, we created Swift as a
language you could reach for,
for C-like performance.
But a key aspect of that is that
we want Swift to have great
inoperability with an existing
ecosystem of C APIs.
And when Swift strings were
using the UTF-16, when you pass
a string off to a C API, there--
unbeknownst to maybe to you, you
had to do-- there was an
allocation and a copy and a
transcoding just to put it in a
compatible format that can be
passed up to six.
So this is like a lot of
overhead.
By moving to UTF-8, we can just
now pull a pass off a
null-terminated UTF-8 string to
C APIs.
No allocations, no copies, zero
overhead.
We've also been able to expand
out the optimizations of the
string type itself.
So string has a small string
optimization where if the number
of characters in the string are
about like 15 characters or
less, we don't need a separate
allocation to care-- you know,
to have that, that payload of
characters, we can just pack it
right into the string value
itself.
So this is a real win.
With Swift 5, we'd be able to
expand this optimization out to
include, you know, essentially
all unicode characters not just,
you know, ASCII, which means it
now applies to languages with
non-Roman characters.
And we've done this while
maintaining great inoperability
between NSStrings and String at
a performance level.
But the really exciting to hear
is this is really all about
performance.
I've said it several times.
A great example that benchmarks
this is SwiftNIO.
For those of you who are not
familiar with it, SwiftNIO comes
from the Swift on server world
and it is a cross-platform
framework for building network
protocols and services.
And it's really been tuned for
speed.
And by switching to UTF-8, we
see a 20% increase in the
throughput of a web server built
on SwiftNIO.
So this is just like, you know,
just a benchmark of textual text
processing.
And this really resonates with
we want string to be a type that
you can use for high performance
intensive string operations but
also just it's user friendly and
easy to use.
And before I hand the stage off
to Anna, who will talk about
mainly the language changes in
Swift 5 and Swift 5.1, I want to
talk about some of the core tool
and improvements and part of
those being a key aspect of the
open source project.
Now Swift being open source is
more than just the day to day,
you know, engineering work on a
project.
It's about Swift being part of a
much broader and diverse
ecosystem of software.
So for example, the Swift
community came together and
created official Docker images
for Swift that are hosted on
Docker Hub.
And if you have Docker installed
on your Mac, which is a few
keystrokes, you can have-- you
can pull down a Docker, this
Docker image and have, you know,
working Docker Linux container
on your Mac that includes a
compiler and the package
manager, everything you need to
get going.
And this was done because
containers are viewed as an
intrinsic part of building
services today.
Another important technology
that is open source is
SourceKit.
And it's a semantic code engine
behind Xcode's features like
code completion,
jump-to-definition, refactoring,
and more.
And it's something that we
continue to refine.
We want to make the results of
code completion, so forth, much
better.
So something we continually to,
you know, iterate upon.
But also we want to make it
reliable and robust.
One of the efforts that we did,
part of the open source project
this year was build a new stress
tester tool for SourceKit.
What it does is it just pummels
SourceKit with all the queries
the IDE could issue to flush out
issues with SourceKit.
So things like crashes,
assertions, it creates
reproducible test cases for us.
And there's something very Meta
here.
We believe in building the first
in class tools with Swift for
all of you and investing in
tools as part of our own
workflows is like, you know,
we're eating our own philosophy
and efforts like this are now a
core part of, you know, our day
to day engineering with the
project.
And I like to talk about a
future looking investment with
SourceKit, and that's in
adopting the language server
protocol.
So take this example slide
before.
You could take Xcode and really
generalize this picture, be
about really any kind of editor
or tool.
I mean SourceKit is open source.
It's designed to be used as a
reusable component for building
tools.
So this could work, right, but
this is really kind of an old
model.
You imagine that there's a wide
range of tools and editors and
IDs out there and they want to
wire up to a variety of
different, you know, language
services.
And so while they could directly
wire up logic to talk to
SourceKit, this is all very ad
hoc.
You have to have each editor
wire up its own support and they
have to understand the SourceKit
and then they have to understand
all the other services they want
to connect to.
So this isn't a very scalable
model.
But like most problems in
computer science, you can solve
everything with a layer of
indirection, (except
performance), and an industry
standard solution called the
Language Service Protocol has
emerged or LSP.
And the idea is, is that if the
editors speak LSP, which is a
standard set of queries and the
services, you know, can speak it
back, you can just mash, make
them up together.
This is an active effort that's
underway, also an open source,
you can check it out.
But to give you a taste of
what's there, this animation
shows code completion support
working in them using SourceKit
LSP and there's also support for
various other editors.
You can find instructions on the
GitHub page.
[ Applause ]
It's investments like this that
are really exciting for us,
because Swift is really-- it's a
language that we built for
general, you know, computing,
right?
It has an immense amount of
potential and this is really
about making Swift really thrive
in a diverse software ecosystem.
With that I'd like to hand the
stage off to my colleague Anna
Zaks [phonetic], who will talk
about the language changes in
Swift.
[ Applause ]
>> Thank you, Ted.
Ted talked about the
improvements to the project and
the compiler.
Now let me tell you about
improvements we've made to the
Swift language and the standard
library in Swift 5 and Swift
5.1.
Many of these features continued
refining the core parts of the
language and the library.
Also, aligned with Apple
shipping several major Swift
frameworks this year, we've
added features that support
creation of better Swift APIs.
As many of you know, Swift
language goes through open
evolution process.
And these SE numbers you see on
the slide correspond to the
feature documents that you can
find on the Swift evolution
website.
It's a great resource if you
want to learn more about these
features.
But now, let me give you a
glimpse into some of them.
We'll start with a few examples
that I just fill in the missing
blocks.
Many of you love the simplicity
of single expression closure
syntax.
So they need to write a return
in single expression, functions,
methods, and subscripts might
feel like unnecessary burden.
Well now, you can use the simple
syntax everywhere.
[ Applause ]
Another [inaudible] was fixed by
an open source contributor,
Alejandro Alonso.
And what's amazing, he is just
finishing high school.
Consider this struct that
conveniently defines default
values for both of its
properties.
Previously, you could call an
initializer and pass it no
arguments.
You could also call an
initializer and pass it all
arguments, but you could not
call an initializer and pass it
only some of the arguments.
As of Swift 5, this problem is
fixed.
Everything works as you expect
[applause].
And the compiler generates
initializers for all of these
cases.
Another imported area is high
performance computing.
In Swift 5 the standard library
added support for SIMD, Single
Instruction Multiple Data
instructions and types.
These are often used for writing
low level performance sensitive
code for graphics, such as image
processing or AR.
In fact, the new RealityKit
library we're shipping this year
is using these types.
The new SIMD types represent
fixed size SIMD vectors.
And as you expect, you can use
the standard library integer and
floating-point types as elements
here.
Let me give you a taste of what
you can do with these types.
You can initialize SIMD vector
from array literals.
Here we have array of size four,
actually two arrays of size
four.
And the new dot operators allow
you to perform pointwise
operations on these vectors,
such as equality and
comparisons.
For example here, we are
checking if x is greater than y
pointwise.
And the result tells us that x
is greater than y only at the
last two points.
The result is stored in another
type called SIMDMask and the dot
operators on the SIMDMask type
allow you to manipulate these
resulting masks further.
For example here, we are
negating the result of the
previous computation.
Swift 5 also gives you more
expressivity for manipulating
text.
String interpolation has been
redesigned in Swift 5.
The new design is up to 1.7
times faster and further, you
can customize the built
interpolation by providing your
own helpers and you can
initialize your own types with
interpolated strings, giving
string interpolation cost and
meanings.
Swift can support the string
interpolation from the very
beginning.
If you write the backslash
followed by some parenthesized
quote inside of a string
literal, the compiler will
execute the code and insert the
string and insert the value into
the string.
This has always worked but there
were some limitations as well.
For example, using an
interpolated string enclosed to
NSLocalizedString does not work.
The interpolation happens before
the translation.
And for example here, the string
file does not contain a
translation of this string with
every single integer inserted
into it.
So, you have 10 apples will not
get translated.
Instead what you need to do is
first construct the
formatString.
Next, localize it.
And only after that, insert the
value into the localized string.
This is the right way to do
string localization with UIKit
and AppKit.
But the new string interpolation
design lets us take it another
step forward and design really
expressive APIs like text from
the SwiftUI framework.
Text is used to represent label
in SwiftUI and we want that
localized.
Let's see how that works.
Here we are passing an
interpolated string to the
initializer of text.
And the trick here is that the
text initializer does not take a
string type as its input.
It takes another type called
LocalizedStringKey that is
defined inside of a SwiftUI
framework.
Because of this, the Swift
compiler uses the cost and
conformance to the expressible
by string interpolation protocol
to process this interpolation.
Once it knows which conformance
to use, the compiler translates
this string interpolation with
this autogenerated code.
Let step three to understand
what it does.
First, Swift creates an instance
for a builder specifically for
the LocalizedStringKey.
This instance will contain two
things, formatKey and an
argument array separately.
Next we build up the string by
processing the segments of the
interpolation.
First, we have a string literal
and we add it to the formatKey.
Second, on processing quantity,
restore the format specifier in
the formatKey and the value
inside of the arguments array
separately.
Last, we add another literal to
the formatKey.
And finally, the initializer for
the LocalizedStringKey is
called.
At this point you have enough
information to properly localize
the string.
So, SwiftUI can use this
language feature to localize the
text, and the user can read the
message.
Pretty cool.
[ Applause ]
This example only scratches the
surface of what you can do with
string interpolation.
And if you're as excited as us
about this new feature, a good
place to get started is reading
documentation for the
ExpressibleByStringInterpolation
protocol.
Now, let's talk about focus.
Part of API design is deciding
what to exclude from your API.
And in Swift 5.1, we've
specifically made improvements
around returned types.
While it's important that the
returned type represents
capabilities of the type that
the user of your API should
reason about, sometimes we want
to abstract what we return.
A function could return multiple
times at runtime, or maybe it
always returns the same type,
but that type might leak
implementation detail about your
API,
exposing something that the user
of your API should not reason
about.
Let's take a look at the options
that Swift provides for these
cases.
We'll use the simple shape API
in our examples.
Here as you'd expect we have a
shape protocol and we have types
that define basic shapes like
circle, oval, and square.
Further we have structs that
manipulate shapes creating their
union and transforming them.
Consider this face shape
example.
Note that this API returns
different types depending on the
type of your shape-- face but
all of them conform to the shape
protocol.
So it's a great place to use the
protocol type as our returned
type.
Now, what about this example
where we're constructing an
eight-pointed star by creating a
union of a square and the
transformed square?
Declaring the concrete return
type here, will leak most of the
implementation details to the
client and expose-- exposing
this unnecessary detail will
make the API hard to reason
about.
However, using a protocol type
shape here is also not so great.
Let's see why.
When the protocol type is
returned, there is no guarantee
that the same type will be
returned from every call to the
API, which, in addition to the
Swift generic's model, brings us
these fundamental limitations.
If you have two values of
eight-pointed star, return from
the same API but two different
calls to this API, they might
not have the same type so you
cannot compare them for
equality.
The returned type cannot have
any associated types nor it can
have requirements that involve
self.
Further, losing this type
identity may prevent some
compiler optimizations.
Swift 5.1 introduces another
concept called opaque results
types.
It's a great feat for APIs that
are known to return the same
concrete type, but might want to
hide them, this type from their
users.
And opaque result type is
spelled some shape and it
conveys that a specific shape
type is returned from this API.
This guarantee of type identity
also allows us to perform
stronger type checking inside of
the body of the API.
So, if you have several return
statements that return different
types, the compiler will cache
that and remind you to fix the
problem.
Opaque result types are
available in Swift 5.1 and you
can read more about them in our
documentation.
Note that this feature requires
new Swift runtime.
So it will only work on newer
Swift OS.
If you had caught that backward
deploy, you can use this feature
but you need to guard their uses
with static availability checks.
Now let's talk about code reuse,
and the new feature called
property wrappers.
Custom patterns for accessing
properties are common.
Some of you-- Some of these
patterns have first class
language support, such as lazy,
but you are also probably
writing your own custom
wrappers.
Maybe you have some storage that
access a thread local
[inaudible].
Maybe you have computed
properties to store your user
defaults.
We write custom getters and
setters all the time, but
sometimes that code is repeated.
For example here, I have two
properties that specify my user
default.
But most of this code is just
copy and pasted.
With property wrappers, we can
declare one type that specifies
the access pattern.
Let's call it user default.
Further, we tell the compiler
that this type is special.
Its primary purpose is to wrap a
property, specify its access
pattern.
What that gives us is this type
will allow us to use a custom
attribute to declare properties
that use-- user default access
pattern.
Let's take a closer look.
With the property wrappers in
place, we can rewrite that two
user default properties from
before was this code.
There is no repetition here.
It's very clean.
All I needed to do is add the
custom attributes, and also know
that these properties still are
declared a type Bool.
So you can use them as if they
were simple Boolean values.
[ Applause ]
Property wrappers allow us to
define custom access patterns
and a property can opt into
using them by just adding a
custom attributes to its
declaration.
We reach for specific tools to
solve specific problems.
They each are useful in their
own domains.
Similarly, DSLs play an
important role in programmer's
lives.
We use them to query databases
and build graphs.
We love the declarative style
that allows us to simply and
concisely declare our layout for
our web pages.
However, they are also
different.
Every time we use one as a
contact Switch, each language
comes with its own syntax and
semantics.
They each have unique powerful
tools that support them.
It's very easy if an HTML tag is
missing, if you're inside of an
HTML editor.
However, because syntax and
semantics are tuned to specific
purposes, the tools that support
them are also often domain
specific.
So when we need to integrate
these DSLs into our projects,
the options are not so great.
In some cases, we add custom
build phases but often we reach
out to this solution.
I'm sure it looks familiar.
It's a string literal that
represents HTML.
We gained integration but we
lost tool support.
The compiler code completion see
this as a string.
There is no type checking here.
It's a blob of text to the Swift
compiler.
So silly mistakes like
forgetting a closing tag go
unnoticed until runtime.
We want the power of these DSLs
but we also want them to
integrate well in our language
and tools.
In Swift 5.1, we are bringing
the power to define embedded
DSLs into Swift.
[ Applause ]
Let's take a look at this code
that defines an HTML object.
One of my colleagues prototype
support for the HTML DSL, just
for fun, in a few hours, using
this new Swift feature.
As you can see here, this code
looks like Swift, but your eyes
are drawn to the HTML elements
is defining.
Here you can see the familiar
Swift concepts like closures and
method calls.
We are using the variables from
our Swift program.
The tools will ensure there are
no unbalanced text and provide
syntax highlights and
refactoring actions.
Our vision is that not only
you'll be able to declare a list
of elements, but you could use
Swift control statements like
this, right here in this DSL.
OK.
[ Applause ]
Let's take a look at how this is
implemented under the hood.
The DSL implementer added a
function to construct each HTML
element.
These functions, they're
closures.
And the interesting part here is
that these closures are special.
They all have this custom
attribute, @HTMLBuilder, that
tells the compiler to use the
HTML builder type to process
these closures.
Let's see how disclosure
containing DSL code gets
translated into a normal Swift
closure.
What is this DSL closure doing?
Well, it's producing a batch of
values.
However, those values are not
used and there is no return
statement here either.
To make this work, the compiler
translates this code by first
collecting the unused values,
and second, calling into the
builder functions to combine
them.
These functions are provided by
the HTML builder type that you,
the DSL author, write and it can
construct any object that is
suitable for your DSL.
Here we are building HTML, so it
builds HTML objects.
We are very excited about this
feature and we use it to power
the declarative syntax that you
will use with the new SwiftUI
framework.
[ Applause ]
Here is an example of that
[inaudible] in SwiftUI using its
own custom Swift DSL.
This feature is available in
beta 1, and we'd like to see how
it will benefit you and what
cool DSLs you'll build with it.
We are discussing the details
behind this feature on the Swift
forums right now.
So if you're interested in
shaping the future of this
feature or other Swift features,
we welcome you to participate.
To conclude, many of the
improvements I talked about come
together in the new Swift
frameworks we are shipping this
year.
And we are excited to see how
they will benefit you, to make
your APIs expressive, clear and
easy to use.
Our colleagues will give a talk
on modern Swift API design where
they will share with you some of
the lessons learned when using
these features building Apple
frameworks.
But this is it from us, enjoy
the conference.
Thank you.
[ Applause ]