Developer Tools • iOS, macOS, tvOS, watchOS • 37:29
Hear about the latest advancements in Swift, the safe, fast, and expressive language. Find out about improvements to build times, code size, and runtime performance. Learn how to take advantage of new features in your code that eliminate boilerplate, increase safety and security, and improve your overall development productivity.
Speakers: Ted Kremenek, Slava Pestov
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
Good morning and welcome to What's New in Swift. This has been an exciting year for Swift and the Swift Community. And over the next 40 minutes Slava and I are thrilled to give you an update on the major developments there. The session is roughly broken in to two parts.
First I'm going to give an update on the Swift open source project and the community around it. And then we'll dive into Swift 4.2, which is available as a preview today in the Xcode 10 beta. Since late 2015, Swift has been an open source project on GitHub. With a vibrant community of users who discuss, propose, and review all changes to the language and standard library.
In that time over 600 individuals have contributed code to GitHub as Swift open source project. And together they have merged over 18,000 pull requests. Since Swift.org was launched Swift has been available for download on Swift.org both for downloadable tool chains for Xcode, as well as various version of Ubuntu. These are both development snapshots and official releases.
Now we want Swift to be supported on all platforms so everyone can use it. And a critical part of that is extending the testing support provided to the community. Various folks in the open source project are working on bringing Swift support to other platforms. And we'd like to support those efforts.
About a month ago we extended the public continuous integration systems to support community hosted CI notes. So if you are a member of the community interested in bringing Swift to another platform or effort, you can now seamlessly plug in your own hardware support to bring testing there. This is a nice prerequisite for supporting Swift in other places.
We've also invested tremendously in the community around the Swift open source project. This is the community that discusses all changes to the language. About six months ago we moved from using mailing lists, which were very high traffic to forums. This was at the behest of the community. Various people were concerned that they wanted to be able to engage in the project at a level that worked well for them but found it difficult to do so. With the forums you can now engage the level that works well for you. The forums have also worked so well that we wanted to extend their utility out to supporting the general open source project.
If you maintain a Swift open source project such as a popular Swift library, you can now use the forums for use to discuss things on that project such as discussions with your users or development. We've also looked at ways to continue to extend Swift.org to be of general use the community. This week we've moved to hosting the Swift programming language book to swift.org. Located at docs.swift.org, this will be a natural place for us to extend more documentation for use by the community.
Now the really exciting thing about Swift is that people are really thrilled about using it. And they're talking about it in a variety of places. At Podcasts, Meetups, conferences. And we, Apple, thought it was very important for us to engage in those places because that's where a lot of the discussion is happening.
Over the last year we have made a very conscious effort to engage in those conferences and present technical presentations on things that we're doing with Swift, or how does Swift work, or how you can get involved in the open source project. And this is something we're very committed to continuing going forward.
One of these efforts I'd like to talk about is a event co-located next to WWDC on Friday and that is the try! Swift San Jose Conference. There there will be a workshop with members from the community to help on board people who are interested in contributing to the Swift open source project. And there will be members from Apple's compiler team there to also facilitate those conversations.
So that's all about the community update. Let's talk about Swift 4.2. I think the natural place to start is well, what is this release about and how does it fit into the bigger picture? Swift updates occur -- some major updates occur about twice a year. And Swift 4.2 is a major update over Swift 4.1 and 4.0.
Now there are in broad strokes two themes to this release. One is a huge focus on developer productivity. You can see this in a variety of ways. The faster builds for projects. But also just through and through massive improvement to the core tooling experience from the Debugger through the Editor. And the community has also focused on language improvements that aim to improve common developer workflows, remove boilerplate.
And Apple has continued invested improvements to the SDK so that the Objective-C APIs better reflect into Swift making better use of the language and improving your use of our APIs. And the other side there's been a massive effort on under the hood improvements and changes to the runtime towards this binary compatibility goal, which culminates in Swift 5, which will be released in early 2019.
So what is binary compatibility? Binary compatibility means that you can build your Swift code with the Swift 5 compiler and layer. And at the binary level it will be able to interoperate with other code built with that compiler or any other compiler layer. This is a very important milestone for the maturity of the language. And what this will enable is Apple to shift the Swift runtime in the operating system, which means apps can directly use it, meaning that they no longer need it included in the application bundle.
So this is a code size win but it's also important that it impacts things like startup time, memory usage, it's an overall huge win for the community. If you're -- we've been very transparent on the progress towards ABI stability or binary compatibility. You can follow along on the ABI stability dashboard on Swift.org.
Today's focus is on Swift 4.2, which is an important waypoint toward Swift 5. Let's talk about source compatibility. So just like in Xcode 9, Xcode 10 shifts with one Swift compiler. So if you're using Xcode 10, you are using Swift 4.2. However, just like in Xcode 9, the compiler also supports multiple language compatibility modes. Now in all the modes you can use all the new APIs. You can use all the new language features. What these gate are source-impacting changes.
The first two are ones that existed previously in Xcode 9. They're there to provide an out of the box experience that you can build your Swift 3 and Swift 4 code without modifications. The Swift 4.2 mode is almost identical to the 4 mode except it gates those SDK improvements that are talked about. That's it.
Just some previous versions of Xcode, there's Migrator Support that you can find in the edit menu to mechanize most of your changes. I want to give an important disclaimer about the Swift 4.2 SDK changes. Later Xcode 10 betas will likely have further SDK improvements. This is to provide opportunities to incorporate you feedback from the betas of how these APIs should be improved and how they reflect into Swift. This means if you migrate to 4.2 early, you should expect there are going to be some changes later. Or you can hold off and migrate later. It's completely up to you.
Now with Swift 4.2 we think we are rapidly converging on what Swift code is going to look like going forward. This is an important phase in the maturation of the language. And thus we really think it's important for everyone to move off of Swift 3 and embrace using Swift 4.2. There are important code size improvements there and just overall improvements to the language. And this is Xcode 10 is going to be the last release to support that Swift 3 compatibility mode.
So let's talk about some improvements to the tooling. In the State of the Union we mentioned that there are some significant improvements to build improvement for Swift projects over Xcode 9. And so these numbers are run on a 4-Core MacBook Pro i7. Let's look a little bit closer at one of them.
This project is a mix and match of Objective-C in Swift. It started out as an Objective-C project and started incorporating Swift. This is a very common scenario. Now what this Build time improvement doesn't really underscore is how much faster building this Swift Code actually became. So if we just focus on how much faster the Swift code built, it actually builds three times faster than it did before. And so that's why the project has that more modest 1.6x speedup.
And what you will see is that the overall build improvements will depend on the nature of your project, how much Swift code it's using, the number of cores on your machine, but we've in practice have seen from many projects it's a 2x speedup. And the win comes from observing that because within a Swift target you have cross-file visibility, right, that's one of the great features of Swift where you don't need header files.
There was a lot of redundant work being done by the compiler. And what we've done is we've retooled the compilation pipeline to reduce a lot of this redundant work and make better use of the cores on your machine. And that's where these speedups come from. If you're interested in more details there's these two great talks later this week that dive into how the build process works under the book including more details about where this performance win comes from. Now this big win comes from debug builds.
I want to focus on how this is surfacing in the Xcode build settings. Recently we separated out compilation mode from optimization level. Compilation mode is how your project builds. So for release builds the default is whole module compilation that means all the files within your target are always built together. This is to enable maximum opportunities for optimization. It's not the amount of optimization done but the opportunities for optimization. And for Debug builds the default is incremental. That means not all the files are all built, re-built always all together.
So this is a tradeoff in performance for build times. Optimization level for Debug builds continues to be no optimization by default. This is for faster builds and better Debug information and the release builds are optimized for speed. We'll get back to the optimization level in a few minutes.
All right so this separation of compilation mode and optimization level nicely highlights and interesting stopgap measure that various folds discovered that when they combined whole module compilation with no optimization that they sometimes would get faster Debug builds. And this is because that combination reduces a lot of that redundant work that I talked about before that we have no made great efforts to eliminate or significantly reduce.
The problem with this combination is, is it impedes incremental builds. So anytime you touch a file within a target the whole target gets rebuilt. Now with the improvements in Xcode 10 to Debug builds, we feel you no longer need to use the stopgap measure and we have observed that the default incremental builds are at least as good as this combination or better. Especially since they support incremental builds.
Let's talk about some important under the hood runtime optimizations and this is all part of that march towards binary compatibility. Swift uses automatic memory management and it uses reference counting just like Objective-C for managing object instances. On this slide I've illustrated in comments where the compiler inserts, retains, and releases. This is how it behaved in Swift 4.1.
When an object is created there's a +1 reference account associated with it. What the convention was if the object is passed off as an argument to another function, it's the obligation of that function call to release the object. So it's basically you're passing off the responsibility to the call to release it.
This provided some performance opportunities to shrink the lifetime of some objects to like their smallest range of use. However, code often looks more like this where you're passing the object off several times to different APIs. And because you have this calling convention, you still have this dance where the initial reference count is balanced out with the final call. But the intermediate calls are expected to have these extra retains and releases because that's what a convention is.
This is really wasteful because the object is really just going to be alive during the entire duration of this function. So in Swift 4.2 we changed the calling convention so that it was no longer the callee's obligation to release the object. This means all these retains and releases go away, which is a significant reduction in retained release traffic. This has two implications. It's both a code size win because those calls are gone and it has a runtime improvement.
Another important optimization we did was to string. And Swift 4.2 string is now 16 bytes big where it as previously 24. We feel this is a good tradeoff between memory usage and performance. It's also, however, still large enough to do an important small string optimization. If the string fits within 15 bytes then the actual string is represented directly in the string type without going to the heap to allocate a separate buffer to represent the string. This is obviously also a memory win and a performance win. This is as similar to an optimization that exists within a string. We can actually represent larger strings.
Finally before I hand it off to Slava we'll talk about the language improvements. I want to talk a little bit more about the efforts to reduce code size. I talked a little bit about that calling convention change, which reduces code size. But we've also introduced a new optimization level, Optimize for Size. This can be useful for applications that care very much about app size limits such as from cellular over the air download limits.
Swift is a very powerful language with static knowledge about what your program does. And so compiler has many opportunities to do performance optimizations such as function call inlining, speculative devirtualization, which trade off a little bit of code size for more performance, but sometimes that more performance isn't really needed in practice.
This is the result of applying Osize to the Swift Source Compatibility Suite, which contains an assortment of projects from GitHub, frameworks and applications. And what you'll see is a wide range depending on what language features are used about 10% to 30% reduction in code size. Now this, when I talk about code size I'm talking about the machine code that is generated as a result of compiling your Swift code, not the overall app size. The overall app size depends on assets and all sorts of other stuff.
In practice we observe that runtime performance is usually about 5% slower. So you're trading off for a little bit of performance. For many applications this is totally fine. So it really depends on your use case. But if this is something you're interested in we encourage you to give it a try. With that I'd like to hand it off to Slava who will talk about all the great language and improvements with Swift 4.2
[ Applause ]
Hey everybody, I'm Slava Pestov. I work on the Swift Compiler and today I'm going to talk about how the new language features in Swift 4.2 allow you to write simpler and more maintainable code. So before we start talking about the new language changes, let's review the process for making improvements to the language.
So as Ted mentioned, Swift is an open source project, but it also has an open design. This means that if you have an idea for improving the language, you can go and pitch it on the forums and if the idea gains enough traction and crystalizes into draft proposal, you can submit it together with implementation to the core team for review. At this point a formal review period allows members of the community to give additional feedback and then the core team makes a decision as to whether to accept the proposal.
If you go to the Swift Evolution website, you can see a list of all the proposals that were accepted and implemented in Swift 4.2. And if you look at this list of proposals there's a lot here. There's more than I can cover today. But one thing I really wanted to emphasize was the large number of proposals that were both designed and implemented by the community. What this means is that these proposals address common pinpoints in the language that came up in the real world and you came up with the idea for fixing these pinpoints and you contributed these improvements back to Swift so that everybody benefits. Thank you.
[ Applause ]
So for the first improvement we're going to see how to eliminate a common source of boilerplate when working with enum's. So let's say I have to find an enum. And I want to print every possible value that this data type can have. So in Swift 4, I had to define a property perhaps with a list of all the possible cases.
And if I add a new case then I have to remember to update that property, otherwise I just get incorrect behavior or runtime. And this is just not very good because you're repeating yourself to the compiler. So in Swift 4.2 we've added a new CaseIterable protocol and if you state a conformance to this protocol, the compiler will synthesize an all cases property for you. OK, that was short and sweet.
[ Applause ]
For the next improvement we're going to see how to eliminate another source of boilerplate. This time it's when you're unable to make your code sufficiently generic. So in Swift 4 we have this contains method on sequence. And this requires that the element type of the sequence is Equatable so that it can find the element that it's looking for.
And of course I could call this within an array of strings because string is Equatable. But what if I call it within an array of arrays. Well array of Int, the element type here is not equitable, which meant that I would just get a compile time error. And you might ask, well why doesn't the standard library make all arrays Equatable. But that doesn't make sense either because if the element type of the array is not Equatable, like a function perhaps, then you can't really make the array Equatable either.
But certainly if the element type of the array is Equatable then I can define an equality operation on arrays that just compares the elements pair wise. And this is what conditional conformance allows a standard library to do. So now array gets an implementation of Equatable for the case where the element type is equitable.
And in Swift 4.2 this example we saw earlier now works. And in addition to arrays being Equatable the standard library defines a number of other conditional conformance. For example, optional and dictionaries are now Equatable when their element type is Equatable and the same works for Hashable, Encodable, and Decodable conformances.
[ Applause ]
And this allows you to compose collections in ways that were not possible before. So here I have a set of arrays of optional integers and everything just works. If you want to learn more there's a session later this week where you can learn about conditional conformance and some other generics improvements in Swift 4.2 that I won't be covering today.
So what about defining your own Equatable and Hashable conformances. Well, a common pattern in Swift is that I have a struct with a bunch of stored properties and all those stored properties are themselves Equatable. And then I want to make the struct Equatable just by comparing those properties of the two values.
In Swift 4 previously you had to write this out by hand. And this is just boilerplate. If I add a new stored property to my struct, I have to remember to update this implementation of Equatable and it's easy to make a copy and paste error or some other mistake. So in Swift 4.1 we introduce this ability to synthesize the implementation of equality. If you emit the implementation than the compiler will fill it in for you as long as all those stored properties are themselves Equatable. This also works for Hashable.
Now what about generic types? So here I have a data type whose values are either instances of the left type or instances of the right type. And I might now want to make left and right constrained to Equatable because again, I want to be able to use this either type with functions, errors, and other non Equatable types. But certainly I can define a conditional conformance so that if left and right are both Equatable then either is Equatable. But I can do even better than this. Notice how the implementation of equality here there's only really one obviously correct way to do it.
You have to check that both values have the same case and if they do you check their payloads for equality. So you might guess, well the compiler should be able to synthesize this for you and it can in Swift 4.2. And this also works for Hashable. So now I can have a set of either Int's or strings as one example. OK.
[ Applause ]
Now, there are cases where you really do have to implement equality and Hashing by hand. So let's look at one example of that. Let's say I have a data type that represents a city and it's got a name, it's got the state that it's located in, and it has the population.
And let's say that for the purposes of this example I only have to compare the name and the state for equality and if I know those are equal I don't have to check the population. So if I let the compiler synthesize the implementation of equality here it's going to do unnecessary work because it's going to be comparing that population field. But I certainly write it out by hand and maybe in this case it's not too bad.
But what about Hashable? So if I want to calculate the Hash code of the city object, then I'm going to calculate the Hash code of the name and the Hash code of the state and I have to combine them somehow. But how do I do that? Well, I can use an exclusive or operation or I could use some random math formula that I found on the Internet or just came up with myself.
But neither one of these is very satisfying and it feels like these Hash combining functions have a lot of magic to them. And the cost of getting it wrong is pretty high because the performance properties that you expect to get from a dictionary or a set really rely on having a good high-quality Hash function.
There's also a security angle here. So if an attacker is able to craft inputs that all Hash to the same value and send them to your app over the Internet somehow, then it might slow your app down to the point where it becomes unusable creating a denial of service attack. So in Swift 4.2 we've added a better API for this.
Now recall the Hashable protocol in Swift 4 and 4.1. It has a single Hash value requirement that produces a single integer value. In Swift 4.2 we've redesigned the Hashable protocol so now there's a different Hash into requirement. And instead of producing a single Hash code value, Hash into takes a Hasher instance and then you can feed multiple values into the Hasher, which will combine them into one Hash code.
So going back to our example of the city data type, all we have to do is implement Hash into by recursively calling Hash into on the name and the state passing in the Hasher object instance that we were given. And the Hash combining algorithm in the Hasher, it does a good job of balancing the quality of the Hash code with performance and as an added layer of protection against denial of service attacks, it uses a random preprocess seed, which is generated when your app starts.
And we think that it should be pretty easy to migrate your code to using the new Hashable protocol and we encourage you to do so. The one caveat to watch out for is you might have some code where you're expecting that Hash values remain constant from different runs of your app or that if you iterate over a dictionary or a set you're going to get the elements in the same order.
And this is no longer the case because of that random preprocess seed. So you will need to fix your code. And to make this easier we've added a build setting, the Swift Deterministic Hashing Environment Variable, which you can enable in the scheme editor to temporarily disable that preprocess random seed.
OK, so let's talk about generating random numbers. So how do you generate random numbers in Swift today? Well, you have to use imported C APIs. And this is really not ideal because they are different between platforms and they have different names, different behavior, so you have to use build configuration checks. But also they're quite low level and these common operations that are not quite so obvious to implement.
For example, if I want to get a random number between 1 and 6, then I might think to just call this Darwin arc4random function and then calculate the remainder of dividing by 6. But that actually gives you a result that is not uniformly distributed between 1 and 6.
So in Swift 4.2 we've added a new set of APIs to make this kind of thing easier. First of all, all the numeric types now define a random method that takes a range and returns a number uniformly distributed in that range. This uses the correct algorithm and it even works for floats.
[ Applause ]
For higher level code we've added a random element method to the collection protocol. And just like min and max this returns an optional so that if you pass in an empty collection you get back no. And finally there's a shuffled method on collection where this gives you an array with a random permutation of the elements of that collection.
And we think the default Random Number Generator is a good choice for most apps. But you can also implement your own. So there's a random number generator protocol and once you write a type that conforms to this protocol you can pass it to all these APIs that I talked about which have an additional overload with a using parameter that takes a random number generator.
OK, so we saw these build configuration checks earlier. Let's talk some more about them. Well, this is a pretty common pattern in Swift. You have a little piece of Swift code that is shared between iOS and macOS and on iOS you want to do something with UIKit. On macOS you want to do something similar in AppKit.
So if you want to do this today you're going to write a #if compile time condition check and then you have to list out those operating systems where UIKit is available. So but what you really care about is not that you're running on this particular operating system, but that you can import UIKit. So on Swift 4.2 we've added a has import Build Configuration Directive so you can better express your intent.
[ Applause ]
And with the new features of Swift 4.2, I can actually improve this code further. So let's say that I'm also going to explicitly check for AppKit and then if neither UIKit nor AppKit is available, for example if I'm building on Linux, I'm going to use the new #error build directive to produce a friendly compile time error message.
OK, now here's another similar source of boilerplate. So if I want to compile something conditionally when I'm in the simulator environment, then today in Swift 4 I have to copy and paste this ugly thing everywhere I want to perform that check. In Swift 4.2 you can use the new hasTargetEnvironment condition, to again better state your intent and just explicitly ask the compiler, am I compiling for the simulator or not? And while we're at it, let's replace that FIXME with a #warning build directive to produce a message or compile time so that I don't forget to fix my FIXME. OK, so that about wraps up all the features that I'm going to discuss today, but I have a couple more things to talk about.
Let's unwrap, Implicitly Unwrapped Optionals. That's a horrible pun. OK, so Implicitly Unwrapped Optionals can be a little bit confusing and let's first review the mental model for Implicitly Unwrapped Optionals. How do I think about them? Well, so since Swift 3 they're not the type of an expression. Don't think of it as a type. Instead, think of Implicitly Unwrapped Optionals as an attribute of a declaration.
And what the compiler does when you reference such a declaration is it will first try to type check it as a plain optional and then if that doesn't make sense in the context where it's used, it goes ahead and unwraps it and then type checks it as the underlined type.
So let's look at an example of the first case. So here I have two functions, the first of which produces and implicitly unwrapped optional integer and the second of which takes a value of any type. And I'm going to call the second function with the result of the first function. Now in this case I can store an optional Int inside of an Any and so no forced unwrapping is performed. The value simply becomes a plain optional. Let's look at an example of the second case now.
Here, the first function now produces -- sorry, the second function now takes an integer. So when I call the second function with the result of the first function then I cannot pass an optional Int where an Int was expected. So the compiler has to insert a force unwrap and then it all works because now I have an Int and an Int And this mental model makes Implicitly Unwrapped Optionals very easy to reason about.
But until recently the compiler had some edge cases where it did not always follow this model. So recall that you cannot have an implicitly unwrapped optional that is part of another type. And this is still the case in Swift 4.2. I cannot have an array of implicitly unwrapped Int's.
However, in Swift 4 previously, there is some edge cases like this. I could define a type alias where the underlying type was implicitly unwrapped Int and then I could make an array of this type alias and I would get very confusing behavior from the compiler that made code hard to understand. So in Swift 4.2 we've re-implemented Implicitly Unwrapped Optional so that it exactly matches the mental model I outlined earlier and this confusing code example now generates a compile time warning and the compiler parses that as if it was just a plain array of integers, of optional integers.
Now, most code will not be affected by this change to Implicitly Unwrapped Optional, but if you were accidentally relying on these edge cases I encourage you to check out this blog post on Swift.org that goes into a lot of detail and has a lot of examples about what changed and how.
OK, now there's only one more thing here today. Let's talk about memory exclusivity checking. So if you recall, in Swift 4 we introduced something called Memory Exclusivity Checking, which was a combination of compile time and runtime checks that restricted certain operations from being performed. In particular we banned overlapping access to the same memory location. What does this mean? Well, let's look at an example.
So here's a piece of code that implements a data type for operating system paths. And this is represented as an array of path components. And there's a withAppended method. This method adds an element to the array, then in calls a closure that you pass in and then it removes that element from the array. And this code is totally fine, it's a valid Swift 4 code. But let's look at this usage of our path data type.
So here I have a path that's stored and a local variable and then I call withAppended on it and inside the closure I access that local variable again printing it. So what the problem here? Well, it turns out this code is actually ambiguous because when I access that local variable inside the closure, it's already being modified by this withAppended method, which is a mutating method. So the ambiguity is that do I mean the original value of path as it was before I called withAppended or do I mean the current value that is being modified whatever that means.
Well, in Swift 4 this was a compile time error because it was an exclusivity violation. And one way to address this is to resolve the ambiguity by telling the complier, hey I really want the new value so I'm going to just pass it in as a parameter to the closure instead of capturing it.
OK, but now look at this example. So this is almost the same function except that it's generic, it's prioritized by the return type of the closure. And in this case we can have the same kind of ambiguity by accessing the path value from inside the closure. But previously Swift 4 did not catch this error at compile time. In Swift 4.2 we've improved the static exclusivity checking to catch ambiguities like this in more cases. And in addition to improving -- OK, and you can also fix the ambiguity in the same way by passing it as a parameter to the closure.
In addition to improving the static checks, we've also added the ability to use the runtime exclusivity checks and release builds. And this has some overhead but if your app is not performance critical, we encourage you to try this out and leave it on all the time. In the future, we will get the overhead of these dynamic checks down to the point where we can leave this enabled all the time and it will give you an extra level of protection just like array bounce checking or integer overflow checking today.
And there's a lot more in Swift 4.2 that I didn't talk about today. And we encourage you to try it out on your existing apps. We also want you to try out the new features and if you have any questions please come to the labs and ask us. Thank you.
[ Applause ]