Join us for an update on Swift. Discover the latest advancements in runtime performance, along with improvements to the developer experience that make your code faster to read, edit, and debug. Find out how to take advantage of new language features like multiple trailing closures. Learn about new libraries available in the SDK, and explore the growing number of APIs available as Swift Packages.
Hi. I'm Ted. And welcome to "What's New in Swift." Today, the ecosystem around Swift is blossoming in many directions. The introduction of API stability in Swift 5 brought binary frameworks to Swift. This led to the advent of powerful new APIs, such as SwiftUI, that are front and center to the Apple developer experience. Over the last year, many investments have been made in Swift, both on the surface and under the hood, that will amplify Swift impact on Apple's APIs, the core of its operating systems and the Apple developer ecosystem.
There's also a growing set of powerful open-source APIs that take full advantage of Swift, which are conveniently accessible to app developers in Xcode using the Swift Package Manager.
Finally, Swift's reach as a cross-platform language for tackling problems in many domains continues to grow with support for more platforms than ever before.
Across both releases, there has been a continuum of investments in performance, language refinements, and the developer experience.
Let us start things off by looking at some significant advances in Swift's runtime performance.
The first piece to look at is code size. Code size is the part of the app that represents the machine code representation of the app's logic. This has been a focus of optimization for several releases now.
To track progress, we have been using a Swift rewrite of one of the apps that ships with iOS. We've been tracking the code size of the Swift version of the app versus the Objective-C version since Swift 4.
We started out with the Swift version at about 2.3 times the code size of the Objective-C version.
Swift 4.1 took a big chunk out with the optimize for code size optimization setting.
At each subsequent release, we narrowed the difference. With Swift 5.3, we're down to below one-and-a-half times the code size of the Objective-C version. Note that some amount of difference is inevitable because Swift comes with safety features that take some amount of code size to implement in an app. Different styles of applications, however, produce different binary sizes. This application was a UIKit app. But what about a SwiftUI app? In Swift 5.3, there are significant improvements to the code size of SwiftUI apps. Here's MovieSwiftUI, which is an excellent open-source app by Thomas Ricouard. We see that its application logic code size is reduced by over 40%. Now, binary size is essential for things like download times, but when you're running the app, it's part of what we call clean memory. That's memory that can be purged because it can be reloaded when needed. So it's less critical than dirty memory, the memory the application allocates and manipulates at runtime. Let's next look at how dirty memory usage has significantly improved.
Swift's use of value types has some fundamental advantages over reference type based languages. To understand why, here's an example in Objective-C of a simple model type with a UUID, a string, and a number. Let's look at how this model is held in memory when we store it in an array.
In Objective-C, object variables are just pointers. So the array holds pointers to the model objects.
Those objects, in turn, hold pointers to their properties. Every object you allocate has some overhead and performance and memory use. This is so important that Objective-C has a special small string representation for tiny ASCII strings that allow them to be stored within the pointer, which saves on allocating the extra object.
Now, we'll look at the same model in Swift.
Swift's use of value types avoids the need for many of these values to be accessed via a pointer. Thus the UUIDs can be held within the Mountain objects.
And Swift's small string could hold many more characters, up to 15 code units, including non-ASCII characters.
Finally, all the Mountain objects can be allocated directly within the array storage. So, with the exception of a few strings, everything is held within a contiguous block of memory. Swift programs can get a significant memory benefit from using value types like this.
So if we examine the heap memory used by an array of 400 of these model objects, we can see the Swift model data is more compact. About 20 kilobytes instead of 35 kilobytes. Note these measurements are from Swift 5.1.
Despite this advantage, some Swift programs previously still used more heap memory because of runtime overhead. Previously, Swift created a number of caches and memory on start-up. These caches stored things like protocol conformances and other type information, as well as data used to bridge types over to Objective-C.
All language runtimes have some overhead, but in Swift's case, it was too large. This was a big focus for us to optimize.
I'm happy to say that in Swift 5.3, we've cut that overhead way down to the point where the Swift version of the app now uses less than a third of the heap memory it did in last year's release. To get full advantage of these improvements, an app's minimum deployment target needs to be set to iOS 14. But many of these improvements will benefit apps that back deploy to earlier OS releases.
In most applications, these differences are not often noticeable, but optimizing Swift's memory like this was critical to allow us to push Swift further throughout Apple system to be able to use it in daemons and low-level frameworks where every byte of memory used counts.
To that end, we've also made another important change. We have moved Swift's Standard Library so that it sits below Foundation in the stack. That means it can actually be used to implement frameworks that float below the level of Objective-C, where previously C had to be used.
Next, let's turn our attention to how we continue to refine the developer experience.
Starting with looking at how diagnostics, errors and warnings from the compiler, have vastly improved in this release cycle.
The Swift compiler has a new diagnostic strategy that results in more precise errors that point to the exact location in source code where the problem occurs.
There are new heuristics for diagnosing the cause of issues that lead to actionable errors with guidance on how to fix issues.
Here's an example of an incomprehensible diagnostic the Swift 5.1 compiler would have produced on SwiftUI code a year ago.
Fast forward today, the diagnostic is significantly better with an error that tells you exactly what is the problem.
When diagnosing issues, the compiler internally also records more information about problems. So it now produces additional notes. These notes can help the developer in a variety of circumstances to better understand and resolve an issue.
In this case, applying the fix-it naturally guides the developer to provide the missing pieces to an incomplete initialization of a Text-Field.
If you're interested in finding out more about the new diagnostic architecture in the compiler, you can navigate over to Swift.org and find a great write-up in a blog post.
Code completion for Swift has also dramatically improved in this release. All the way from the code completion inference provided by the compiler and SourceKit, and through the experience in the Xcode code editor. Let's turn our attention to how completion results have fundamentally gotten much better.
First, the inference of candidate completions has significantly improved. Here the compiler is inferring the value in a ternary expression when used within an incomplete dictionary literal. This simply would not have worked before.
Code completion also provides the values you would expect for some of the more dynamic features of the language, such as using KeyPath as functions.
Besides the quality of completion results, code completion performance has drastically improved. In some cases, up to 15 times speed improvement compared to Xcode 11.5.
This is particularly beneficial for editing SwiftUI code. These bar charts show the performance for common uses of code completion in SwiftUI code measured in seconds. In Xcode 11.5, these actions would take around half a second to complete, which is quite noticeable. Now, these are under a tenth of a second.
Code indentation in Xcode, also powered by the open-source SourceKit engine, has significantly improved. You will see improved handling of chained method calls and property accesses, a major component of Swift UI code. You'll also find improvements for alignment of call arguments, tuple elements, collection elements that span multiple lines, and multi-line control flow.
Here you can see one of the improvements at work in SwiftUI code taken from the open-source MovieSwiftUI project. Before, you would get sometimes a natural indentation for some of the chained accesses.
But now, they are cleanly visually aligned. These, and the other improvements I mentioned, will have a noticeable effect on your editing experience. Next, let's talk about improvements to the core experience of debugging Swift code.
When debug information is available, the debugger will now display the reason for common Swift runtime failure traps instead of just showing an opaque "invalid instruction" crash.
Further, Swift debugging support is now more robust in general. To understand why, let's look at a key way Swift interoperates with Objective-C at compile time.
Swift imports APIs from Objective-C using Clang modules. To resolve information about types and variables, LLDB needs to import all Swift and Clang modules that are visible in the current debugging context.
While these module files have a wealth of information about types, since LLDB has a global view of the entire program and all of its dynamic libraries, importing Clang modules can sometimes fail in ways they would not at compile time. One common failure scenario is when the search pass from different dynamic libraries are in conflict.
As a fallback, when this occurs, LLDB can now also import C and Objective-C types for Swift debugging purposes from DWARF debug information. This vastly increases the reliability of features such as the Xcode variable view and expression evaluator.
Swift is an excellent general-purpose language. Swift is great for building apps on Apple's platforms, but also great for many other tasks.
For this reason, we think cross-platform support in Swift is so important.
Swift's official support for more platforms continues to grow.
This year, Swift updated its support for Ubuntu, as well as picked up official support for more variants of Linux with CentOS and Amazon Linux 2. And coming for Swift 5.3 will be the initial support for Windows. Follow along on Swift.org to find out more as it becomes available. With these ports comes increased opportunity to use Swift in more places.
One of those places is AWS Lambda. Serverless functions are an easy way for client application developers to extend their applications into the cloud. It is now easy to do this in Swift using the open-source Swift AWS runtime.
The runtime is 100% open-source and available on GitHub, and is the result of a group effort that included engineers across the Swift community. It includes instructions, how to build and deploy to AWS Lambda and programming to AWS Lambda. As this example shows in Xcode, the amount of code needed is as simple as writing "Hello, world". Those are just some of the exciting updates in the Swift ecosystem. Next, Kyle will talk about changes to the Swift language and libraries.
Thanks, Ted. Hi, I'm Kyle, a member of the Swift Standard Library Team.
It's been a big year for the Swift language and the Swift Library ecosystem. Let's start by talking about the language. In addition to the improvements Ted just discussed, we've added over a dozen new language features across Swift 5.2 and Swift 5.3. Because we don't have time to cover all of them today, I want to draw your attention to these SE numbers.
As many of you know, the Swift language goes through an open evolution process. Each one of these numbers corresponds to a feature document that you can look up on the Swift Evolution website. This website is also a great jumping-off point if you're curious about how you can help shape the future of Swift. All right, let's dive in. Some of the most exciting additions this year are the powerful new tools available to API designers, which is all of you, whether you maintain a popular open-source package, work on a team with other developers, or just use Swift for your own personal projects. If you code, you design APIs. Let's begin with multiple trailing closure syntax. Since its inception, Swift has supported something called trailing closure syntax, a bit of syntactic sugar that lets you pop the final argument to a method out of the parenthesis when it's a closure. Trailing closure syntax has proven to be very popular. And it's not hard to guess why. It can be more concise and less nested without loss of clarity, making the call site much easier to read. However, the restriction of trailing closure syntax to only the final closure has limited its applicability.
In this case, the trailing closure makes the code harder to read because its role is unclear. Worse, it changes meaning from the completion block at one call site to the animation block at another. Concerns about call site confusion have led Swift style guides to prohibit the use of trailing closure syntax when a method call, like this one, has multiple closure arguments.
As a result, if we ever need to append an additional closure argument, many of us find ourselves having to rejigger our code more than may seem necessary.
New to Swift 5.3 is multiple trailing closure syntax.
This extends the benefits of trailing closure syntax to calls with multiple closure arguments, and there's no rejiggering required to append an additional one.
Multiple trailing closure syntax is also a great fit for DSLs.
SwiftUI's new Gauge View is used to indicate the level of a value relative to some overall capacity.
Here I have a circular watchOS gauge for tracking the acidity level of my garden soil. But tomatoes are finicky enough to where I'd really like to see the exact value at a glance.
Fortunately, I can add a currentValueLabel and minimum and maximumValueLabels. By taking advantage of multiple trailing closure syntax, Gauge is able to elegantly, progressively disclose its customization points. Let's take a moment to explore the implications of trailing closure syntax on Swift API design. Imagine you encounter this line of code in, say, a messaging app you're working on, where summary is used to show a snippet of the last message in each chat. What's the value of summary? What's the call to the take method doing? The closure is probably determining which characters to take, but how's it being applied? Pause and think about it for a second.
Is it taking characters that match the predicate and dropping those that don't? Is it taking characters while they match the predicate and dropping everything that follows? In this case, the call site is only clear if we use the longhand spelling that includes the argument label.
We can do better. Because folks can drop the argument label, it's best to name methods assuming that they will.
A better name for "take" might be something like "prefix," which suggests the result is anchored to the start of the collection. In fact, prefix(while is the name that was ultimately chosen for this method when it was added to the Standard Library way back in Swift 3.1.
The same guidance applies to more complex APIs, including those with multiple trailing closures.
It's important the base name of the method clarify the role of the first trailing closure. Because its label will be dropped even if it isn't the first argument to the method.
Next, let's discuss KeyPath expressions as functions.
Back in Swift 4.1, we introduced smart KeyPaths, types that represent uninvoked references to properties which can be used to get and set their underlying values.
When you're designing an API, KeyPaths are a tempting alternative to function parameters if you expect the call site to be a simple property access, because they're more concise and less nested. For this example, I've borrowed an algorithm from Ruby. Chunked. It's an adapter over a collection that groups adjacent elements while their keys are equal. I happen to keep a list handy of my friends' and family's shoe sizes. A new pair of shoes always makes a good present.
I can get a quick overview by sorting, and then chunking my contacts by shoe size.
Hmm. If I ever need to re-gift a pair, it looks like I should try Charles. Of course, every once in a while, you're going to encounter a use case that isn't a simple property access.
Say you're shopping for sandals which don't come in half sizes.
To support both of these call sites, you'd have to duplicate the declaration. New to Swift 5.2, you can use a KeyPath expression as a function.
This means you can pass a KeyPath argument to any function parameter with a matching signature. And you can delete any duplicate declarations you may have added in the past, in order to accept KeyPaths.
Next, I'll introduce you to @main, a tool for type-based program entry points. Every program needs to start execution somehow. Here, I'm using the new ArgumentParser package to declare a simple tool that prints "hello" to a name drawn from the command line.
But the boilerplate required to start execution is as long as the program itself, and it seriously kills the declarative vibe.
Since Swift 1.0, you've been able to use the UIApplicationMain attribute on your AppDelegate to tell the compiler to generate an implicit main.swift that runs your app. In Swift 5.3, we've generalized and democratized this feature.
If you're a Library author, just declare a static main method on the protocol or superclass you expect your users to derive their entry point from.
For ArgumentParser, that's ParsableCommand.
This will enable your users to tag that type with @main and the compiler to generate an implicit main.swift on their behalf. This standardized way to delegate a program's entry point should make it easier to get up and running, whether you're working on a command line tool, an existing application, a new one, or something else entirely.
We can't wait to see how you put these powerful new API design tools to use.
Next, I want to highlight some language enhancements that eliminate boilerplate and increase expressivity.
Let's start with the increased availability of implicit self in closures.
In order to draw attention to potential retain cycles, Swift requires the explicit use of "self" in escaping closures which capture it. But when you're required to include many self.s in a row, it can start to feel a bit redundant.
New to Swift 5.3, if you include "self" in the capture list, you can omit it from the body of the closure.
You're still required to be explicit about your intention to capture self, but now the cost of that explicitness is only a single declaration.
Sometimes, however, even a single use of self. can feel unnecessary. Like in SwiftUI, where self tends to be a value type, making reference cycles much less likely. Well, with Swift 5.3, I'm pleased to tell you that if self is a struct or enum, you can omit it entirely from the closure.
We think these refinements will increase the signal-to-noise ratio of compiler errors related to the implicit use of self in closures, mitigating the temptation to slap on self. without due consideration.
Next, let's talk about multi-pattern catch clauses.
Historically, do-catch statements have not been as expressive as switch statements, leading folks to resort to nesting switches inside of catch clauses. Well, in Swift 5.3, we've extended the grammar of catch clauses to have the full power of switch cases.
This allows you to flatten this kind of multi-clause pattern matching directly into the do-catch statement, making it much easier to read.
Next, let's look at a couple enhancements we've made to enum types.
Since Swift 4.1, the compiler has been able to synthesize equatable and hashable conformance for a wide variety of types.
Sometimes, though, you run into situations where it'd be awfully convenient to have a comparison operator. I'm pleased to tell you that in Swift 5.3, the compiler has learned how to synthesize comparable conformance for qualifying enum types.
Next, let's discuss enum cases as protocol witnesses.
Consider these two call sites.
Is fileCorrupted a static var or an enum case? Is keyNotFound a static func or an enum case? Static var and static func: enum cases.
The call sites for the two declarations are identical.
In recognition of this, in Swift 5.3, we've enhanced enum cases so they can now be used to fulfill static var and static func protocol requirements.
Last year, Swift added support for embedded DSLs to power SwiftUI's declarative syntax.
This included builder closures to collective view's children, and basic control flow statements like if-else.
I'm excited to tell you that in Swift 5.3, we've extended embedded DSLs to support pattern matching control flow statements like if-let and switch.
Here, I'm able to compose a photo gallery of my favorite animals by using a switch statement to alternate between the different image layouts within this scrollable vertical stack. Next, I'll introduce you to builder inference.
Here, I've written an app for keeping track of the books I'm currently reading in my book club.
I've got a main window for my primary user interface and a Preferences window for my app settings. Previously, to use DSL syntax at the top level of body like this required tagging it with the specific builder attribute. With Swift 5.3, I'm excited to tell you that builder attribute will no longer be required because we're teaching the compiler how to infer it from the protocol requirement.
SwiftUI added a ton of exciting new features this year. If you want to know more, I recommend you watch the "What's New in SwiftUI" session.
Now I want to change gears and talk about some of the powerful, new Swift APIs available to you, beginning with those in the SDK. Let's start with Float16.
Float16 is an IEEE 754 standard floating-point format that's new to Swift 5.3.
As the name suggests, a Float16 takes just two bytes of memory, as opposed to a single-precision float which takes four.
Since it's half the size, you can fit twice as many of them in a SIMD register or page of memory which, on supported hardware, generally results in a doubling of performance.
Beware, though. As a smaller data type, it also has a more limited precision and range, so be careful translating code that was originally implemented for double or float to work with Float16.
For more on Float16, I recommend you watch the session we've prepared on "Numerical Computing in Swift." Next, let's talk about Apple Archive, a new modular archive format based on the battle-tested technology Apple uses to deliver OS updates.
It's optimized for fast, multithreaded compression and comes with Finder integration, a command-line tool, and a host of other powerful features, including an idiomatic Swift API.
This is how simple it is to efficiently compress and archive a source directory using Apple Archive.
I want to draw your attention to the FileStream constructor which leverages another new library we're introducing this year: Swift System.
Swift System provides modern idiomatic interfaces to system calls and currency types for low-level system APIs like Apple Archive.
The raw weakly-typed interfaces imported through the Darwin overlay can be finicky and error-prone.
Swift System wraps these APIs using techniques such as strongly-typed RawRepresentable structs, error handling, defaulted arguments, namespaces, and function overloading, laying the groundwork for a more idiomatically Swift system layer of the SDK.
Last, let's look at some enhancements to OSLog.
OSLog is a unified logging API that's been optimized to have minimal overhead and crafted to prevent inadvertent logging of sensitive data.
In Swift 5.3, we've leveraged sophisticated compiler optimizations to make OSLog dramatically faster and more expressive, adding support for string interpolations and formatting options. If you're still using print as your logging solution, now is the perfect time to reconsider.
If you want to learn more about these APIs and how to leverage logging to track down hard-to-reproduce bugs, I suggest you watch the session on "Logging in Swift." Last, let's turn our attention to the growing number of APIs available to you outside the SDK via the Swift Package Manager. Let's begin with Swift Numerics, a new open-source package for numerical computing.
Swift Numerics defines all the basic math functions like sine and logarithm in a way that's much more useful for generic contexts, as well as support for complex numbers and arithmetic. Swift Numerics complex numbers are layout compatible with their C counterparts, but faster and more accurate.
There are many other exciting projects being actively discussed right now on the Swift Numerics GitHub page, including approximate equality comparisons, arbitrary-precision integers and decimal floating-point numbers.
The Swift Numerics package is discussed in-depth in the session we've prepared on "Numerical Computing." Next, let's look at Swift Argument Parser, a new open-source Swift package for command-line argument parsing. Earlier, we saw how to use Argument Parser to print "hello" to a name drawn from the command line.
Let's extend this by adding an integer option, count, with a default of one, and a loop to print the greeting "count" number of times.
Now our tool can be that much more enthusiastic.
It's also smart about what values it accepts, guides users towards correct usage, and includes a richly-documented help screen.
All this and more from a program that comfortably fits on a slide.
Last, I'll introduce you to the Swift StandardLibraryPreview Package. The Preview Package provides access to functionality that's been accepted through the Swift Evolution process, but hasn't yet shipped as a part of an official Swift release. By getting new features into your hands sooner, we think we can make Swift even better.
As part of this effort, we've also made it easier for Swift Evolution proposals to proceed to review. In the past, that would have meant building the entire compiler stack. Now, you can provide the implementation for a StandardLibrary feature proposal as a stand-alone Swift PM package.
We've seeded the preview package with the functionality from SE-0270, which includes operations on sub-ranges of collections and the supporting range set type. I encourage you to try it out.
The future of the Swift Library ecosystem is being molded right now as we push onto new platforms and into new domains, and it's being done in plain sight as we increasingly leverage open-source packages. We're deliberately releasing these packages early while they're still malleable and developing them as community efforts on GitHub.
So try them out. File an issue. Open a pull request.
It's never been a better time, and it's never been easier to get involved and make an impact. I hope you're as excited about these new Swift releases as we are. We can't wait to see what you build.
Enjoy the rest of WWDC. Thank you for watching.
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.