Featured • iOS, macOS, watchOS • 50:18
Swift continues its rapid advancement with version 2. New optimizations make your app run even faster, and new syntax makes your code more expressive. Learn about powerful new features like error handling, testability, and availability checking. This session will help you write even better apps by moving to Swift 2.
Speakers: Chris Lattner, John McCall
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
[Chris Lattner]
[ Applause ]
I'm Chris Lattner. I'll start off this talk and my colleague John McCall will take you through the second half to take you through what's new. Before we get going, I thought it would be interesting or helpful to look at what we're trying to do here. What are the goals and what are the philosophies behind Swift 2? We had three big things we were going for.
First, fundamentals. We want the core features and the core behavior of the language and the tools to be great. A lot of this is taking a look at the feedback that many of you have produced in the process of using Swift. So a lot of what this is is -- I want to thank you for all the feedback you have. You guys are continuing to shape Swift through all the great feedback the producers have that use it. Second up is safety.
Safety is a core value of Swift. We really want it to be easy to write safe code by default, and we think that the new availability in error handling constructs will be a great new way to do this. Third, beauty. We want your code to be beautiful. As programmers, we work with code all the time. This is quite important to us. We have added new things to Swift making it easier to write more beautiful and natural code.
Today we'll talk about five new things in Swift. Before we get diving into what's new in Swift 2, I think it is important to point out that Swift 1.2 was also a huge update. It was released just three months ago. Because of time limitations, we don't have time to talk about it much. But if you're interested and you haven't seen it already, check out the Swift programming language book and the Xcode 6 release notes. Let's dive in and talk about fundamentals.
Fundamentals is about refining the core behavior of the language and how it works together. So there are a lot of little things here. This will feel like a bit of a random walk, but stick with me. We'll start off with enums. Enums are one of Swift's best features. Here I have an enum enumerating some common household pets.
Enums are great because they're simple to define and use. On the other hand, if you have played with them in a playground or printed one out, you may have been left wanting for more. In Swift 2, enums now carry enough reflection information that you can print them, and they work great.
[ Applause ]
[Chris Lattner]
Next, associated values. Enums are also great because they're the perfect model for discriminated union, which is great when you have two values of different types that you want to store in one thing. Right? Associated values are very powerful, maybe you went and tried to write the obvious thing in either type. This is a perfect way to model this, but when you went to use it, you got something depressing like this. Well, this has been very sad, and none of us like this. Now with Swift 2 it just works.
[ Applause ]
[Chris Lattner]
Let's talk about recursion. Enums in Swift are actually algebraic data types, and in other languages, recursive algebraic data types are really powerful. You can do some really great things. The problem is in Swift, the values in an enum are stored inline. This means if you have a recursive enum, it has an infinite size, which is hard for our current devices to hold. Maybe next year.
There are workarounds for this. Everybody has seen probably the box-type that you can turn into a reference, but that breaks pattern matching, it is ugly and horrible. With Swift 2, there is a better way. It didn't quite make it into beta 1, but coming soon you'll be able to mark your cases indirect allowing you to express this naturally, and pattern matching works great. Let's move on and talk about scoping [applause].
[Chris Lattner]
So sometimes you have a name that you want to reuse or maybe a resource that you want to make sure is released early. We have introduced a new Do statement allowing you to introduce an explicit scope. In this case, we all have to deal with Internet trolls now and then, but we try to keep them bound as tightly as possible.
Do is really important when we bring up error handling later in the talk. But taking Do as a keyword led to some potential ambiguity. It's not ambiguity for the compiler; it's ambiguity as we read code. You don't always see the bottom of a big, long statement, and we have a Do While loop.
To make it superclear by just looking at the introducer for a statement of what it does, we want to make it superclear, what something does by looking at the introducer keyword for the statement. So we have taken the Do While loop and renamed it to Repeat. You can immediately tell from the top it that it is a loop, making it really easy [applause].
[Chris Lattner]
Let's talk about option sets. Option sets are a lightweight, superefficient way to represent a set of Booleans. You may have seen them if you've worked with various Cocoa APIs, and you use the see the See Like syntax to Or them together. The basic syntax like this is actually pretty nice.
The problem is, when you get to the other syntaxes you end up using, it is a bit less nice. You create an empty-option set with nil -- it doesn't make sense because option sets and optionals are completely different concepts and they're conflated together. You extract them with bitwise operations, which is a pain and super error-prone, and you can get it wrong easily. With Swift 2 we have taken the option sets -- this is even worse, because Swift 1.2 had a first-class set type, and now the combination of all of this makes option sets seem like an archaic throwback to C, which they are [laughter].
[Chris Lattner]
But Swift 2 solves this. It makes option sets set-like. That means option sets and sets are now formed with square brackets. That means you get empty sets with an empty set of square brackets, and you get the full set of standard set API to work with option sets. It is supereasy and great.
[ Applause ]
[Chris Lattner]
Now, it is also nice because you can define your own option sets in an easier way now. Now all you have to do is define your own set type, or struct type, struct type for your set, and have it conform to the new option set type protocol, find storage to hold your bits, and define the elements you want for your option set.
With just a simple definition, you now get all the syntax we talked about. The thing that's supercool about this, it doesn't require any compiler matching. This is done automatically through a new feature called Default Implementations and Protocols provided by option set type. We don't have time to talk about Default Implementations and Protocols in detail here, but we have a session talking about protocols going into it deeply. It is great.
Let's talk about functions and methods. Swift unifies functions and methods together into a single func declaration. This is a great thing that pulls two disparate concepts together in the type system into a beautiful functional core. This beautiful functional core is a key part of Swift that immediately falls apart when you try to call these things because they take different argument labels. This has been a huge pain for a lot of people. If we look at where this came from, Swift was following the precedent of Objective-C.
C doesn't have argument labels, argument labels are superimportant for methods in Objective-C, and Swift followed. With Swift 2, we fixed this and more. Now functions and methods have the same declaration syntax, and they work the same way. Now when you call a global function, you provide argument labels by default. Everything is uniform.
[ Applause ]
[Chris Lattner]
So the key thing to know here is that this affects pure Swift code. If you declare a global function in Swift, you get this behavior by default. Functions imported from C continue to behave in the same way they always have because argument names in C functions are not part of API and are not thought about as API.
But we like Swift code going forward to include argument labels on functions. If you look deeper in what's going on here, there is even more. Swift functions take parameters, parameters can have two different names for each value. So when you declare something with the syntax, you're actually getting a default behavior.
The two names that a parameter can get are an external name that the caller sees and the internal name that the implementation sees. By default that first argument has no label shown to the external client of it and has a name you're probably using when you implement the method.
Likewise, the second and later arguments all default to being the same inside and outside. And that's why you see this behavior of having an argument label for that argument. The great thing about this model is that when you understand this, you can customize it. For example, in this case, it would make sense to have a label on that first argument so you know what it is.
You can do that simply by duplicating that argument name. It is simple. Similarly, if you want to remove something, you can explicitly set the name of that to the underscore to say remove this argument label. In doing so, we have committed one of the most heinous naming crimes imaginable by having a Boolean without a label.
Go me! The even better thing, this whole change, this makes labels much more prominent in the system. This is great for having APIs that are friendly to use and means that we can simplify away a ton of complexity. So now functions and methods work the same, but we can also get rid of special rules for the default arguments and there is the weird pound syntax, nobody remembered what it did, so now that's gone too. It is much better.
We'll talk about the compiler and talk about the warnings and the error messages compiled by the compiler. Here is code that's reasonable code, maybe you have written something like this before, where I'm trying to update a point. If you gave this a Swift 1 compiler, it would produce something like this.
I don't know about you, but that's not helping much. Swift 1.2 made this better. Swift 1.2 made the error message actually tell me that there is a problem. Now I see that indeed I cannot assign to this. Of course, this is not good enough either. We have continued to invest in the error messages and warnings produced by the compiler, and in Swift 2 it says, hey, you can't assign to x because Self is immutable. And Xcode will tell you that you can fix this by marking the method as mutating. This is a great way I think many people -- it will help many people understand the mutability model in Swift better and lead to better code everywhere.
[ Applause ]
[Chris Lattner]
Of course, this is just one example. There is a bunch more. Another example of warnings we have added are for if you have a variable that can be declared as a constant, we now produce a warning, say, hey, use Let instead of Are. The Swift migrator also automatically moves a lot of code to using Let instead of Are in many cases.
We'll warn if you declare a value, either Let or Are, but don't use it. We even have warnings if you use a functional method and then ignore the result because you probably meant to use an in-place mutating method instead, and we can produce warnings for those. Those are simple examples.
Let's talk about the SDK. It's a core part of Swift, it's how well it works with Cocoa. With plain Objective-C APIs, the Swift compiler has no idea whether pointers can be null or not and what the element types of collections are. We have introduced a number of new features to Objective-C including the ability to express nullability and the element types of collections in Objective-C.
There is a whole bunch of other features that make a great experience for Objective-C code in Swift. The best news about this, is that the framework engineers at Apple have done a phenomenal job adopting all these modern Objective-C features, and the Cocoa SDK in general across all the platforms feels great in Swift with no work on your part.
However, if you have Objective-C code, maybe you're mixing and matching with Objective-C code in your project, or maybe you have an Objective-C framework that you want to be beautiful and awesome in Swift, go to some of these sessions later today to learn more about these features so you can provide a really great Swift experience. You probably have to watch one of those on video.
Let's talk about unit testing. Across the entire tools team, testing is superimportant. Testing is great in Swift until you bring up access control. The problem is that Swift requires you to mark symbols Public to be visible to your unit test bundle so you can test them, leading to tons of stuff being public that really shouldn't be. Swift 2 and Xcode 7 has solved this problem. Now your code is automatically built in a special mode, meaning that for your tests they can get access to your public and internal symbols by default. You have to use the new app to --
[ Applause ]
[Chris Lattner]
The even better part of this, not only is it easy, it is also -- you still get the right behavior for your release builds, so you get the performance and the protection benefits of access control. We have a bunch of talks on testing, UI Testing in Xcode will be a fantastic talk. I highly recommend it.
Let's talk about rich comments. Playgrounds, they're great, and Xcode allows you to build beautiful Swift playgrounds right in the editor using a comment syntax, a rich comment syntax. The syntax is a variant of Markdown, which is a great, well-known, very popular, loved syntax. We brought that to documentation comments as well. This means you can build rich and pretty beautiful documentation comments, and it shows up for clients in your API. So if you're producing a library, you can do great things here.
Finally, the migrator in Xcode. As soon as you open the Swift 1 project in Xcode 7, it will prompt you and say, hey, I can upgrade this to Swift 2 for you. It takes you through a couple steps, you can pick your targets, and then it gives you a dif.
The Swift 2 migrator is actually pretty phenomenal. It covers the vast majority of the problems and cases that you will see moving from Swift 1 to Swift 2, including the error handling model, moving things to methods, changes to the SDK, a ton of the option set changes, all of these things are built in the migrator, and it does a great job.
There is a ton of new stuff in Swift 2, we don't have time to talk about all of it right now. If you're interested in more detail, I recommend taking a look at the Swift programming language book, there is a new version up. Also the Xcode 7 release notes talks about a lot of these changes in more detail. Let's move on now and talk about pattern matching.
So probably the first place you encounter pattern matches was with the if-let statement. It is a great way to take an optional, conditionally unwrap it, and then bind that result to a name with safety. It is a great thing. There can be too much of a great thing, of course.
One of the things we saw is that there is the "pyramid of doom," which is what happens when you get too many if-lets all nestled together, and suddenly your code is fifteen levels deep and you can't understand it. Swift 1.2 solved this problem by introducing a compound condition into if statements. Which makes this really natural. You can check multiple optionals and Boolean conditions right inline, and it is a lot nicer. This didn't solve the problem of early exits.
I'll show you some of what might be the most horrible JSON processing code you can ever imagine. It will get better over time. Let's go with this. Here I'm pulling various fields out of an untyped JSON dictionary. So I'm pulling out a name, converting it to string, produces an optional, if it doesn't match, I bail out. Likewise, pull the year out, convert it to an In, if that doesn't match, bail out. This pattern is very common if you're pulling lots of values out, it is better to do this bailout approach than to deeply nest your code.
The problem with this approach is that then you have to force unwrap the optional values when you're done. Here I only use them once, but if you have a bunch of code using them, you're force unwrapping this everywhere. You can do things to factor this, so the implicitly unwrapped optional is a great way to factor force unwraps into a Mecca of unsafety for you.
This is maybe not the right approach either. This is ugly. We introduced a new Guard statement. The way to look at Guard is it does a check and then bales out if that check doesn't match. You can do a lot of things in a Guard statement. Here we're doing our optional check and we're binding a value to a name.
The way that it works, the way it can work, is that it guarantees that your Else exits the current scope. You can do this in one of two ways, either return, throw, brake, a lot of ways to exit a scope. That's fine. You can also call a No Return function like Precondition Fail or Abort, and that's a good way of stopping.
What this guarantees for the compiler, it knows with safety and certainty that the names that are bound can be visible after the code in the fall-through. If we take this to our example, our example gets nicer because now we can use Guard, and we have very safe, concise checks for this as we would expect. The other nice thing about this, this builds on the rich, compound conditionals we had with If. Now you can merge them together and check multiple Boolean and optional and other cases we'll talk about later right in line in your Guard statement. It is pretty nice.
[ Applause ]
[Chris Lattner]
Let's talk about the more exciting and powerful form of pattern matching, switches. Switches are I think maybe other people's favorite feature of Swift because you can do so much with pattern matching in a switch statement. You check against an optional like here, you can do class hierarchy checks, you can check against ranges, there is no end to what you can do in a switch.
They're great when you want to write a lot of cases, but they're kind of syntactically heavyweight when you want to check just one case. They have to be exhaustive, you have to have a default, it is a pain. What we have done, we have taken the power of pattern matching with switch and with case and brought it to the other control statements in the language. This example can be written with a new If case, check, and you can pattern match and bind variable names right in line.
[ Applause ]
[Chris Lattner]
We have gone further. Another great statement in Swift is the for...in loop. So it's very common to want to do some amount of filtering in a for...in loop. Some languages have gone so far as to introduce entirely new language constructs like list comprehensions to model this kind of pattern. With Swift we have done two things. We have added the ability to do a simple Boolean a filter right inline in your for...in statement. But you can also do full-on pattern matching right in your for loop to give you powerful conditions.
[ Applause ]
[Chris Lattner]
That's all I have to say about our quick tour of pattern matching. We talked about the new Guard statement, which is great for early exits, talked about bringing pattern matching pervasively to the language, and we didn't talk about some of the other improvements that you can discover as you start to use Swift. Thank you. I will hand it over to John, who will tell you about availability checking.
[ Applause ]
[John McCall]
Thank you, Chris. We often roll out new features, you may have heard of one called Force Touch. Force Touch is mostly a hardware feature, of course, but it comes with a number of APIs. Like this one on NSButton, letting me change how a button responds to drags over it. If I want to adopt this in my own app, that's pretty easy, right? I have to write some new event handling code and then I just need to take my button and set this spring loaded property on it.
The problem is that this may work great on my dev machine, but when I farm it out to my test hardware, I'm going to get a crash like this almost certainly. That's because this is a new API. It was introduced in X v10.3. And, like many of you in this situation, you still have a need to support an older version of the OS.
How would I fix this? The way I used to fix this is, okay, I'm getting this error message about the method not existing, let me check to see if the method exists. One way -- there are a lot of different idioms that people have developed for doing this, this is common idiom, using Responds to Selector. The problem is that this is a fraught, error-prone pattern.
For example, I actually have to figure out what the selector is, the mapping from some Swift language feature back to some Objective-C selector, it is really not the sort of detail that anybody should need to know. It is also, you know, not being checked for me by the compiler because I'm sort of intrinsically escaping the sort of checking that the compiler provides.
For example, in this case I have actually forgotten to add this colon, which means that check will never actually be true. In Swift 2 we have a better solution. By default, the compiler is checking to make sure that you don't use any APIs that are not available on your minimum deployment target.
[ Applause ]
[John McCall]
If I do something like this, I'm always going to get a diagnostic. What that lets me have is a sort of safe core assumption that as long as my code -- as long as my project fully compiles, it is at least free of this sort of trivial deployment [unintelligible]. Now that's not the entirety of the compatibility story, of course, but this is a great way to help you adopt new APIs. I do want to use this. How do I do that? We added a new #available condition.
In #available, you list out the OS versions you want to make sure you test for. And at the end you use this star to make sure that if there are any new OSs that you haven't written this code for, you at least get diagnostics about the availability there. Here I've used an If statement, but I could have used the Guard statement that Chris showed us before. It is the exact same sort of condition logic in all of these places. That's it.
That's availability checking. We think this is a great new way for you to be able to adopt and take advantage of the new features of new OSs automatically and safely in your projects. We'll talk more about this in a talk later today. I really suggest that you come to it.
The next thing I want to talk about is protocol extensions. Extensions are a really great feature in Swift. I can take an arbitrary type like Array and add my own methods to it. It is not necessarily obvious why this is an important thing, but a method is a core part, it is how the native APIs of that type are expressed. When I'm adding an extension, I'm really adding new functionality to a type that feels just as first class, just as core to the API of the type of anything of the designer of the type may have already added.
That has a lot of advantages. Here I've added a Count If method, that simply calls a closure for every other array and counts up the number of times that return True. There is nothing in this method that's actually specific to array at all. This ought to work for an arbitrary collection.
Unfortunately, in Swift 1 I couldn't express this as a method anymore. To make this generic over an arbitrary collection, I would have had to write something like this. As a lot of you have pointed out to us, this is not exactly optimal. The first thing is that this is a lot of extra syntax, there is sort of a blindness about all of the angle brackets in it, all of the extra crust to just make this generic.
The second thing is it is no longer a method. Because it is no longer a method, first off, it doesn't feel like a natural part of using the type. Second off, it is a lot less discoverable. It won't show up in any lists of the functionality on array, and in particular it's not going to show up in the list of functionality provided by code completion. Which means that great, you have written this awesome Count If thing, but nobody using this will realize it exists unless you point it out to them.
All right. Let's go back. We had this extension on array just to add the method to array. Why can't we just extend -- I don't know -- everything that implements collection type? In Swift 2, I'm happy to say that now you can. You extend collection type rather than extending array.
When you do this, you're automatically adding the method everywhere to every single type that implements collection type, not just from the standard library or anything but even in your own types that happen to conform to this. Not only is that great for writing your own generic code, but we found it really lets us overhaul a lot of things that we weren't really happy with about with the standard library in Swift 1.
There were a lot of things in Swift 1 that had to be global functions because they had to be generic or because we wrote them generic. Then worse, in order to make the methods we sort of special-cased certain types, like array has a lot of these map and filter methods on it. Other types like Set may not have.
In Swift 2, this functionality is going to be available, this sort of filter and map functionality is expressed with extensions, meaning it is available on every single thing. It makes it a lot more discoverable, means using the standard library is more uniform, and we really think that you'll love it.
[ Applause ]
[John McCall]
I really haven't even gone into a third of the complexity in the awesome new power of this feature. We're going to have a talk dedicated to this, it is a great talk tomorrow. I strongly suggest you come to it. It is about the great new design patterns that protocols enable in Swift.
The rest of the talk is going to be about error handling. I don't think anyone really likes thinking about error handling. It is always sort of this guilty thing in the back of our minds if you're at all like me. It is really, really important. When we were looking at what we could do to Swift that would really make it a more robust, more expressive language, we said, okay, the most important thing here is going to be doing something about error handling. When we looked at the solutions that were out there, in other languages, in Cocoa, we weren't really happy with any of them. They all have sort of major pitfalls that we didn't really like.
Some of them, you know, are based around propagating the errors around automatically like with NSError in Objective-C, what that creates is a lot of repetitive error-prone code where you end up having to duplicate logic all over the place. That means it is very easy to get wrong. More importantly, when you propagate error values around yourself, the implicit default behavior is that you're ignoring errors. That's just never the right default. You should have to think about errors at least a little bit.
On the other end of the spectrum, there are languages that propagate errors around implicitly, like with exception handling. But we didn't like how any of those worked either. There is too much that was implicit, it was too easy not to think of errors again, and you end up with pitfalls where you just didn't understand what could go wrong in your program. You didn't understand how control could flow from one place to another. Again, it wasn't a safe, reliable programming model.
There are really three different ways that functions can fail. One way is that they can simply -- a lot of functions just fail in one fairly simple, innate, obvious way. For example, unless you're running a compiler, you probably don't care why parsing an integer out of a string failed, this isn't going to be something that you're getting the juicy details of and reporting to the user. Probably you want to handle that directly. That's already something that when we looked at it, this is handled extremely well in Swift already, just with optional results. We didn't think we needed to do anything here. We're really happy with how that works already.
On the other end of the spectrum, there are a lot of things that are logic failures in your program that are programmer mistakes, assertions, indexes out of bounds, the vast majority of ways in which people use NSException, that kind of thing. For these things, they really actually shouldn't be recoverable. When you can recover from this kind of thing, you're just promoting a less stable program overall. You don't know what state your program is actually in if you randomly recover from an index out of bounds. You could even be creating security problems in your application.
In the middle, there is this large spectrum of APIs that can fail in a very rich set of ways. That's really what we wanted to focus on. The things that you today in Cocoa would use NSError for. I want to work through an example for you. This is a preflight method, I have some sort of operation I want to prefly to make sure it works. This is something that a lot of you have written I think before. I'll check to see whether some file is actually reachable, and then I'm going to reset some state associated with the operation.
Now checking whether the resource is reachable, this is an operation that can fail. It can fail in a wide variety of ways. It should report something back because, hey, somebody calling this really may want to know why something isn't reachable and maybe treat it differently depending on why.
If I wanted to use NSError for this, this is kind of what the code ends up looking like. I'm taking this error, I'm propagating it out to my caller. There are things to like about this. There are a lot of things we don't really like. It adds a lot of boilerplate to my logic.
I had a tight little two-line function, now it is turned into this -- you know, it has this If statement, extra nesting, the extra parameter, there is a lot here whose sole purpose is to express there is an error, and we're propagating it out to the caller. Worse, again, there is a convention here, that's a convention you need to know about.
And it's a convention you have to implement manually, and the compiler's not really going to help you with it. Again, I made a mistake here. The convention is that an error happens when you return False, I'm actually checking it the wrong way. I don't know why they trust me to even work with compilers [laughter].
I need to add this Not here in order to get the behavior I want. Okay. That's -- well, no, sorry. Those are the downsides of this. There is actually a lot that we like about it too. The first thing is, it is obvious from reading this code that check resource is reachable is something that can fail. It says right there in the name, it talks about errors, it has the explicit error handling thing, this explicit error parameter. Similarly, it is obvious that preflight is an operation that can fail. Again, explicit error parameter, the return value, et cetera.
The third thing is, there isn't implicit control flow. I can just look at this thing and understand where all the jumps in it are. I can analyze my code statically as a human, not as a compiler. As a human, I can look at this code and reason about what it is doing without needing to know every last detail of every single function that I'm calling.
All right. Now let's go back to the example. This is how it looked before. What's going to happen if I try to compile this in Swift? I'm going to get an error message because I'm not handling the error. There are two components to handling errors in Swift. The first is that whenever you're calling an API that can fail, you have to use this Try keyword.
The Try communicates, it is really there primarily for someone reading the code. It communicates to you, hey, this is something that can fail. That means when you're coming back, when you're maintaining this in the future, I know straight off -- Reset State, that's not necessarily going to be called every single time to this function.
That may be a really important thing for me to know. When I'm writing this code in the first place, it is something for me to think about. Hey, should Reset State be called every single time I exit the function? For a preflight operation? Maybe. That alone isn't enough, I'm not actually handling the error in any way.
This is because in Swift, by default, functions can't throw. That's actually a really core aspect of our design because what it means is errors are bounded. You don't have to think about literally everything being able to throw an exception like it can in Java or C# or basically every language using exceptions.
Instead, it is really just very specific things that you know you need to worry about whether they can throw. And when you call them in your code, it is always marked with Try. That combination communicates a lot. Okay. Well, suppose I do want to just propagate the error out to my caller. In order to do that, all I have to do is tell the compiler, hey, it is okay for this thing to throw the error out. I do that with throws.
That may not be how I want to handle this. This is a preflight operation. I probably want to just swallow the error and tell whoever is calling me whether or not the preflight succeeded. To do that, I have to handle it. I handle it by writing this Do Catch. Any code that's within the Do, any error arising within it, is forwarded, sort of filtered through all of the catches. So what comes after a catch, well, anything that you could write in a switch. The entire power of Swift's pattern matching syntax is available in a catch.
As a very simple, common syntactic refinement, catches alone like this is a shorthand for catching it and binding this special error variable to it. I could also write a much more elaborate thing. For example, I may want to treat certain kinds of error as special, maybe they're acceptable in my preflight. I don't know why the file not existing would be okay, maybe I really want to check if it exists and, you know, isn't actually usable for some permissions reason. If I wanted to, I can pattern match against the error code and domain directly like this.
As an aside, there is a third way of "handling errors." It does often happen that you set up preconditions such that you know that a particular call to something that can formally throw can't actually throw. For example, maybe this file is actually in my app bundle and I know that if I can't read a file in my own app bundle, something is really, really wrong. There's probably no real way to recover from this.
So with this common pattern, where you really want a fatal error because an error is thrown, it has a very compact syntax associated with it, this Try! All that really does is creates an assertion that the code within the Try doesn't actually throw. If it does, your program will crash just like any sort of assertion failure. That's something that you can debug very, very easily. It is not something you want to use all the time, but when you need it, it is really, really handy. Going back a bit.
I caught an error. What kind of thing is an error? Well, we have a protocol built in to the standard library called ErrorType. You can throw any value whose type conforms to the ErrorType. When you catch something, that thing that you're pattern matching against is an arbitrary value of ErrorType.
We think it is actually really important that we don't track errors more precisely than just whether or not an error was thrown. It is not like Java, where you end up with a pedantic list of every single exception that might have been thrown out and then you end up with this really complicated propagation problem every time you change errors. Just tracking whether an error can be thrown is usually good enough, almost always. We think this is a great model.
You can make your own types conform to ErrorType. This is a process that's a lot easier to do than it was in Cocoa. Enums are a great way of expressing this. They're a great way to express a group of related problems, just like they're -- you know, so that's especially true because you can associate data with each case in an enum. If I want to report a richer error message with maybe something about the -- maybe I'm checking for some invalid state and I want to remember what the invalid state was, I can embed that directly in my enum as an associated value for that particular case.
All you need to do in order to make an enum usable as an error is make it conform to ErrorType. The compiler automatically handles the details of the synthesis. This is much better than the process of creating a new NSError domain and associating things with it. We think that this will really help you make great, expressive error-throwing APIs in your own code when you need to.
Let's go back to that example that Chris had up before, this JSON processor. Here I'm returning an error back using an Either type in the string. Let's make this look more like it would in Swift. The first thing is, instead of the string, I'll use that data error enum I just talked about.
I just have to throw, use the new throw statement in order to throw those values, and that just works. The other side of this, of course, is I'll need to change the return type. I'm no longer returning an Either type. This isn't something that every single caller has to carefully micromanage the return value of in order to check for errors. I just change it so that it returns Person and is also a throwing method, then I don't have to mess around with these little details.
Let's make a new example that culls that method we just created. We parsed out, we have a snippet of JSON, we parsed out a person. Now we'll use that to parse out an entire sales record involving that person and some item. Sometimes it happens that you need to, you know, this is a bit of a contrived example.
Sorry. Sometimes I want to observe this kind of process. I'm going to have some sort of delegate, I'll let it know that I have started reading a sales record. I have now told it I started reading, obviously I should tell it when I'm finished reading. I can add that code down here. The problem is I'm not doing anything right with error handling.
It is really easy for all of these -- like -- if my delegate actually has Careful and Variants set up around necessarily getting called every single time -- getting called when the sale finishes. If my delegate has variance it wants to maintain about getting called on both ends, I'll mess them up if I actually fail the process. This is a sort of problem that comes up a lot and makes error handling seem so fragile.
Okay. One way I could solve this, of course, is that I simply add my call to Did End Reading Sale on both of these throw sites and then, of course, I'm still not handling this call down to Process Person. In order to do something there, I have to add this Do Catch.
This is a really, really -- one, this is incredibly verbose. But it's also really error prone because it is easy for me to add new code to this, new kinds of processing that it then immediately will get out of date if I actually do something. If I forget to add Did End Reading Sale along that particular path. Swift 2 has a much better option. It is called Defer.
A Defer statement creates an action. When you execute it, that action is going to be executed no matter how the current scope is left. If I return out of it, if I fall out of it, if I throw an error out of it, no matter how, I know that that thing is going to be executed. That means that as someone reading this code, maintaining this code, I feel perfectly confident Did End Reading Sale will be executed no matter what I do to finish reading the sale. That's a really, really valuable thing to know.
[ Applause ]
[John McCall]
I want to make a quick note about implementation. Some of you who are used to exception handling may be aware that exception handling in many languages is implemented in a way that's very, very highly biased against errors actually being thrown. It is often three, maybe even four orders of magnitude slower to return out of a function by throwing an error than it is to simply return out in the normal manner. Now, that's necessitated by some aspects of the language designs. It is not really something that we wanted to imitate in Swift.
All you really need to know here is that the Swift implementation here is far more balanced, much more like, basically, an If statement in the caller. That means it is not completely free in order to call something that can throw an error. But it means that you don't have to worry about our error-handling feature being so expensive that you can't use it in order for the actual reasons that you need to if you do need to care about the efficiency of the error path.
Finally, I just want to note, Swift, the Swift error handling design works beautifully with Cocoa APIs. We automatically recognize the most common conventions you see in Cocoa. For example, methods that have an NSError Out parameter and return Bool automatically become throwing methods and the Bool return value goes away. Similarly if it returns an optional result, we recognize that pattern as the nil indicates an invalid thing, and it no longer returns an optional result because the nil, of course, is subsumed within error handling.
[ Applause ]
[John McCall]
Just with these two very simple rules we found that the vast, vast majority of APIs in the system import and automatically work with this new Swift error handling model seamlessly and beautifully, and we think this is a great new way to handle errors in Swift. I really strongly suggest that you check this out. You probably don't really have much of a choice, they're all over the place [laughter].
[John McCall]
You know, we really are proud of this design. We think it is going to greatly improve the robustness and the expressiveness of writing code and let you design your own APIs that work just wonderfully. Let me sum up. We have been working in Swift 2 really hard to present, to give you a new language, really flesh out the core aspects of programming in Swift, using the tools in Swift, giving you a safer, more robust environment, and generally making things great.
An invaluable tool to us, this entire time, has been your feedback. We really, really appreciate it. We are listening, I promise. If you have things to say to us, of course you can simply use bug reporter, but you can also email Stefan Lesser, come on the dev forums, most of us are there all the time. We're really happy to respond to any question, hear your feedback about it. We really, really value you. Thank you very much [applause]. Have a great WWDC 2015.
[ Applause ]