Swift is now the language of choice for a number of major frameworks across all of Apple's platforms, including SwiftUI, RealityKit and Create ML. Join us for a review of Swift 5.0 and an exploration of Swift 5.1, new in Xcode 11. Find out about the latest advancements in performance and safety. Discover new features in the language, and how they have enabled the development of these new frameworks.
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.
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.
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.
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.
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 , who will talk about the language changes in Swift.
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.
Another 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 . 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.
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 . 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.
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. 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.
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.
Here is an example of that 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 ]
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.