Video hosted by Apple at devstreaming-cdn.apple.com

Configure player

Close

WWDC Index does not host video files

If you have access to video files, you can configure a URL pattern to be used in a video player.

URL pattern

preview

Use any of these variables in your URL pattern, the pattern is stored in your browsers' local storage.

$id
ID of session: wwdc2015-411
$eventId
ID of event: wwdc2015
$eventContentId
ID of session without event part: 411
$eventShortId
Shortened ID of event: wwdc15
$year
Year of session: 2015
$extension
Extension of original filename: mp4
$filenameAlmostEvery
Filename from "(Almost) Every..." gist: [2015] [Session 411] Swift in Pr...

WWDC15 • Session 411

Swift in Practice

Developer Tools • iOS, macOS, watchOS • 38:17

Learn how Swift can help you define away some common pitfalls in app development, allowing your apps to benefit from safer runtime behavior while enjoying strong guarantees provided by Swift at compile-time. Hear about how API availability checking in Swift allows you to easily take advantage of new APIs while guaranteeing safe deployment to earlier OS releases. See how enumerations and protocols can help not only maintain compile-time invariants between your app's code and assets but also reduce boilerplate.

Speakers: Ted Kremenek, Alex Migicovsky

Unlisted on Apple Developer site

Transcript

This transcript has potential transcription errors. We are working on an improved version.

Good afternoon. Hi, everyone. Thank you for joining us. My name is Ted Kremenek. I manage the Swift team at Apple. Alex and I have the real honor and privilege to talk to you this afternoon about some great ways to use Swift in practice to find more issues in your code at compile time.

This is a pretty broad theme, so we decided to focus on two topics. The first is about taking advantage of the new language affordances in Swift 2 to easily allow your application to take advantage of new APIs while deploying to earlier releases. We talked about this briefly in earlier sessions.

We are going to really dive into the philosophy of how it's designed, the problems it was solving, and how best to use it in your code. Afterwards, Alex is going to talk about how to use the rich type system, protocols, enums, and even protocol extensions to enforce application invariance in your own code and to define away a lot of boilerplate.

So let's jump into our first topic, taking advantage of new APIs. This is a well-told story that many of you are familiar with. As Apple, we continue to roll out rich, new APIs in every OS release that gives you the opportunity to build great features in your application.

Right? This is part of the reason why we do it; right? The conundrum here is that apps have existing users; right? They are currently... they are not necessarily using the newest OS. We have a rapid adoption rate on iOS, but not necessarily everybody adopts instantaneously, and some of them can't adopt at all for a variety of reasons. So you're faced with a series of choices.

What do you do? Should you just go ahead and require your app to use the latest OS? Right, so you get all the new APIs, but this really sucks because you are going to lose... you know, you lose users. These are the people who are buying your app.

Should you, on the opposite extreme, hold back on adopting new APIs at all? Right, this just goes to the least common denominator of the earliest OS that you support. This is also bad because you are holding back on the possible rich features you could be delivering to your users.

And of course, you know, there's the "have your cake and eat it too," where you can adopt new APIs while still deploying back to earlier releases. So this is something that we have supported technically, you know, for a very long time. Right, you can do this in both Objective-C and Swift. But the reality is this is an extremely painful experience today.

[ Applause ]

But in Swift 2, we tried to make it as pain-free as possible, and we've done that by looking at the current problems that developers have told us about, you know, about deploying to earlier releases and trying to solve that problem in the language. Now, the fundamental model hasn't changed; right? To develop, just for our platform, we always want you to use the latest SDK. Right? That essentially gives you the full grab bag of all the APIs you could potentially use in your application. Then you toggle the deployment target of your app to say how far back in time you want to go.

Now, pictorially, this is pretty simple. It's like a sliding window of releases. You set the base SDK to the latest, set the deployment target back to the earliest release in that window; right? Conceptually very simple. So before I talk about how we do adopt new features and APIs in Swift 2, let's take a look at the current problems we have in the existing approach.

So fundamentally, you have to write your app so it can contend with the absence of APIs on an earlier host OS; right? So there are several things you have to separately consider -- the absence of entire frameworks, classes, methods, functions, and even certain enum values are not legal to use on an earlier release.

And the thing that really is the bummer is you have to reason about each one of these independently. For frameworks, you have to tediously decide, you know, say hey, this framework is going to be optional when I link it into my application. If you don't do this, your app is just going to fail a load when it launches on an earlier OS.

And then comes the actual usage of the API itself. Let's start with classes. So fundamentally, you are writing your app so it behaves differently. There's going to be conditional behavior here, like when new APIs are available, your app is going to do something different. So conditional logic isn't really the problem. The problem is how you actually conditionalize your behavior.

Here on this slide, here is the canonical way that you check availability. You query the Objective-C runtime, say hey, is this class present at runtime? The problem is that this is a bit of a lie. Right? I mean, so the class may be available, but that doesn't mean that it's available for you to use.

Frequently, APIs start their life as internal APIs, right, in the OS, and they go through some time where they are baked and their evolved, and then by the time they are released for public use, the behavior of the API may have completely changed. Even though this check may succeed at runtime, it's not the actual truth of whether or not you can actually safely use this API.

What that means is if you use the API too early, you know, on an OS release too early before you are supposed to be using it, you essentially have a time bomb in your application. The behavior of your app that assumed that that API would behave a certain way is now completely broken. This has bitten developers many times. So this is a huge problem.

The other problem is it's so easy to make mistakes. Take this new API. With a few characters' difference, I have completely valid code, and it's doing the wrong thing. And this data was introduced long before NS data asset. Your code will compile, it will even run correctly if you are testing on the latest OS.

So the failure occurs only when you happen to run this code on an older OS device. Right? And that's not going to be in your common test scenario, and in some cases it's only up to your users to find this problem. And what will happen is you will have a crash at runtime when you try and use this class. So easy to make mistakes just by having simple typos.

Methods essentially suffer from the same problems as classes. You can type them wrong. If you are checking for the availability of a property, now you have to know what the selector was for that property and you have to spell it correctly, and also the syntax is completely different; right? You are checking for APIs, but the syntax is different.

Functions suffer from the same problems. You can make the same mistakes, but yet you have yet another third way to write something. And then if you have enums, you are just completely hosed. You don't really have a good solution. There is no respond to selector for enums, so you are stuck doing a manual OS version check. And just looking at this table, like there's a whole assortment of book keeping that you have to have in your head to get this right. And this is just a very sad story indeed.

So our observations was this is just a really broken programming model. Right? It technically works, but it's just hard to do. And we want you to take advantage of new APIs while continuing to support all of your users. So we need to fix these problems. So in Swift 2, things have changed. It's built into the language.

You focus on how you want to structure your app so that when it makes sense to have alternate behavior, you just go and use those APIs. You understand there's going to be conditional behavior there, but you focus primarily on that. Then the compiler has your back. It will emit an error if you use the API in an unsafe way.

You also have unified syntax, so you don't have to think about classes, methods, functions, even enums. All of this is handled under one syntax that you just use. And the compiler knows the syntax for you to use, so if you don't get it right, it will tell you what to do. And because the compiler is all involved all along, and in Swift we are using modules, all that optional linking, that just gets handled for you.

So how does this work? So here are some APIs from core location. Now, let's say I am deploying to iOS 9, so I am using the iOS 9 SDK and set my deployment target to iOS 9. The compiler sees this information in the SDK, so this is literally in the Objective-C headers, which you could also view as the generated interfaces in Swift. So the class was introduced in iOS 2, and the method was later introduced in iOS 8. Since I am running on iOS 9, there's no problems just using this API unconditionally.

If I deploy back to iOS 8, still no problems. But if I slide all the way back to deploying to iOS 7, the compiler can see, just as we can read on this slide, that it's unsafe to use this method, request when in use authorization. And the compiler will tell you that this is just unsafe code. And it's an error. It will literally prevent you from building this code. And it will give you a nice safety check.

[ Applause ]

It will give you the option of different ways you can solve this problem. There is this note, you want to guard it with a safety check? There is a fix it attached to it, and if you accept it, your code is rewritten as this. Now, there's a combination of compilation, static enforcement, and runtime enforcement here.

What's happening is this hash available syntax. Basically, everything within that block of code the compiler scans to see if, you know, what is the most recent iOS or OS X or whatever release that's needed to execute those APIs safely? And then it will use that version that's mentioned in the hash available to do the appropriate runtime check for you. The compiler will insert that in. You don't have to guess how it's done, it's done efficiently, it's cached, so you can just use it very safely. So just by using the information in the SDK, we get this perfect fidelity, so you get a really safe model.

So a few people have asked why are we checking against OS version? Isn't this something we have provided guidance against doing in the past? The reason is the bookkeeping is so hard to do. At least when you are querying runtime, you get some kind of truth, even though that truth turns out to be a lie in many cases.

Well, logically, when we talk to app developers -- you -- you think about, you know, the experiences you want to build in your app, it's all staged in by, like, what host OS your users are running; right? In each OS release, there's like a wave of new APIs.

Those basically define the set of features that you could implement, and your users are on different versions of the OS, and so they -- you know, they logically break into categories of the behaviors that your apps, you know, can have; right? And so this all just varies logically consistent.

Also, it's not checking for just the existence of one API. It doesn't even really make sense because you usually intend to use a bunch of APIs together. And it's not always true that the existence of one API implies the other. That information is in the SDK, and the compiler can do that bookkeeping for you.

And the compiler being involved is the real game changer here. It makes it so the availability checks are just reliable, and you can assume the compiler is doing the right thing. So you get that peace of mind that you are really defining away the whole class of problems because you have this compile enforcement.

It also naturally goes to multiple platforms as well. So let's say I have this NSData asset example from before. If I want this code to target both OS X and iOS, I can simply extend the syntax to say I am also checking for this minimum availability on this other platform. The star indicates essentially, you know, the all other cases, which in this case would be Watch OS.

We put it there explicitly to call out that there's potential control flow here. For other platforms that aren't explicitly mentioned, this if condition will still be executed. Essentially, there is a true and a false. We wanted to call out that those branches, they still will be taken. So we wanted to kind of explicitly call out that behavior for readability.

Now, the availability check also naturally composes with other affordances in Swift 2 for control flow. So let's say you have structured your app so that you want to do this check, and then implement some feature and otherwise do nothing, just bail out. Well, this composes nicely with the new guard statement, and you can just reshuffle your code like this. Everything below the guard is guaranteed to have the availability provided by the hash available tag.

[ Applause ]

And so this naturally composes to find a nice way to factor your application. So let's say you are deploying back to iOS 7. So I am going to color code the APIs that are available at iOS 7 in green, and the green bar represents within this block of code, it's safe to use anything from iOS 7 or earlier, so this is basically the compilers, you know, view of the world.

If I want to use an iOS 8 API, which I have color coded in orange, you have to do the availability check; otherwise, you get an error from the compiler. You can think of it that within this block of code, you are raising the privileges of what APIs are allowed to be called.

Once I get out of that block, my privileges drop again, where I can only call iOS 7 APIs. If I want to call iOS 9 APIs, I can do a different check which then gives a different range of privileges within that block. It's a very composable, readable model.

Let's say I am building an app that has a whole new different set of features depending on whether certain APIs are available. I want to factor it out. I am not just loading a bunch of code into these if statements. I want to factor it out into separate functions.

This is easily done. I can declare another function. Let's say for pedagogical reasons, my function uses iOS 8. I am going to call that from my conditional block. And the problem here is the compiler doesn't know that you are going -- that you know -- that you are only going to call this once you've done this check.

So by default, the compiler is going to go okay, you are targeting iOS 7. I am going to assume you can only use iOS 7 APIs within this function. So if you want to use iOS 8 APIs, then you have to do the check there. This is not really great; right? This does not provide a way for you to really factor your app. It also results in redundant checks.

You can tell the compiler your intentions. So the SDK itself has these add available adaptations on all the methods and classes, saying this is the minimum OS you can use for this API. You can use those same annotations on your own code. What that means is you can't even call this function unless you've done the appropriate availability check. And once that happens, the compiler sees your code in a different light.

You can then completely eliminate that check, you know, that extra availability check, and safely use iOS 8 APIs. This is nicely composable because you have other functions similarly annotated, and you can just go directly call them, if the functions essentially have the same API privileges. And if you wanted to call APIs with even more privileges, that's when you have to do the availability check. So this is very nice, composable, easy to reason about way about how to factor your code.

This applies, as you would expect, to methods as well, so you can mark -- a class can be available, but individual methods might not be. So if you want, you can possibly instantiate the class before you can call a specific method at a higher availability, you would have to do the check.

This also naturally works if you want to mark the entire class as requiring a certain minimum availability. If you did that, can't even instantiate the class unless you've done the availability check. So you get this really, you know, total transitive closure of completeness of the API availability. And this naturally leads to some really great tricks you can do. So let's say you had some custom, you know, blurring view that you had on earlier iOS releases, and then Apple rolls out a more specific subclass of UI view that you want to use on host OSes using a new release.

You can do this kind of runtime polymorphism with the availability guard so that if you are running on your OS, use the native, you know, UI. Otherwise, use your custom one. And then the client that goes and gets this object back doesn't need to care about which OS version you are running on. You have completely provided this separation of concerns.

And this works really nicely for you can do the same thing with protocols, providing different implementations, you can have closures, different functions. It gives you completely new ways in which to factor your code out and get the safety that you expect. So we think the availability checking is pretty awesome. It -- I think it really provides a cohesive, safe way to adopt new APIs and deploy back to earlier releases.

The unified syntax provides a really safe programming model, but more importantly, it gives you a way to naturally factor your apps. You can read your apps. You can read the code and understand how, you know, the invariance that you are expecting. And I think that's incredibly powerful. And on that note, I am going to hand it over to Alex, who will talk about other ways you can use Swift's powerful type system to enforce your own invariance in your own application.

[ Applause ]

[Alex Migicovsky]

Thanks, Ted. Hi, everyone. My name is Alex Migicovsky, and I am a sample coder here at Apple. For the past year and a half, I have been teaching developers how to write Cocoa apps with Swift. I have started developing lucid dreams about Swift and Cocoa and how you can use the two together to have safe -- compiled time safe applications. What I want to do today is go through some of these visions I've had and explain some of the paradigms you can use from the visions and apply those concepts in your own applications so you can have compile time safe code as well.

Now, I haven't told you this yet, but in these visions, I also dream about unicorns. I have developed a unicorn app that lets me go see and browse through the different unicorns I have seen in my visions. And the first thing that I want to talk about in my application is Asset Catalog identifiers. This is something that everyone uses in their UIKit apps. Now, my unicorn browsing app is pretty simple. I have three unicorn images that I have added to my Asset Catalog.

But what I want to do now is take a look and see what the code looks like when I actually create the images from the Asset Catalog. And you'll notice that I am -- here I have three images that I am constructing. Each of them I am passing a string to the UIKit UI image API.

And UIKit doesn't know what assets I have actually provided in my Asset Catalog, so I have to unwrap these images if I actually want to use them in my application. This is really unfortunate because I have already defined the Asset Catalog identifiers in my Asset Catalog. I don't want to have this duplicate information here.

Furthermore, right here I am only using three images, but throughout my application, I could use many, many more. The problem is it's really hard to find in your code where you might have typos, and you might want to change these based on the reaction that you see on the slide with all these typos. I know I do. But it's really hard to go back and find all that. So a classic solution to that would be to have a global constant where you would use the same constant name everywhere in your application.

And so if you use that correctly, you will get your unicorn image back like you expected, but you still have to unwrap the image because the compiler doesn't know and the framework doesn't know if you are providing a valid constant name or not. And furthermore, you can provide totally random streams to the API and get a fatal error at runtime because it's the NSUbiquity identity change node notification is still a string and is valid to pass in UI image named API.

Let's talk about how we can solve this problem. What we have right now is a stringly typed solution. We are passing strings everywhere in our code. What we really want is a strongly typed solution. What we want to be able to do is map strings to a new kind of type.

This is going to allow us to encode information about how we structured our application right into the compiler's knowledge so that we can return non-optional UI images everywhere in our code. The solution to that is going to be application-specific enumerations, enums that we define in our own application.

If we take a look at the code that we defined before, this is what I really want to have my code look like. I want to pass an enumeration every time I create a UI image object so that I don't have to unwrap the return value in my code. So how do we implement that? Well, the first thing that I want to do is define a nested type on UI image that's going to provide the mapping between enum cases and the string representation we have defined in our Asset Catalog.

We defined it as a nested type so other assets that can be stored in Asset Catalog can have this, can use this approach as well. Once I have done that, it's pretty straightforward. I can provide a case mapping between an enum case and string representation. I can do that for all my other cases as well.

Now, one really beneficial thing about this approach is that if I accidentally have typos, if I copy and paste a string accidentally from somewhere and have a duplicate, the compiler is going to give me a warning or an error telling me that I have a duplicate case in my enumeration.

So that's really great that the compiler can help me with that. Now, once we've defined this new type, all we have to do is go back and write a convenience initializer that takes in this enumeration instead of the string and forwards the enumeration's raw value to the UIKit defined UI image named initializer.

If we go back and look at our code, we get unicorns everywhere we wanted them, and if we look and see what happens when we have a typo like we had before, the compiler now can tell us that we've had a typo since we've encoded that information of our application structure into our code.

So if we fix that, the compiler error goes away. So let's talk about the benefits of this approach. The first thing is we have centrally located constants. If I were to add a new image to my Asset Catalog, I know exactly where I need to go to add another image constant case. The other benefit is this doesn't pollute the global namespace. I could have multiple objects that can be defined in an Asset Catalog that I use this approach for.

One of the best things is that you can only use one of these enum cases in your application when you are constructing your UI image object now, so the compiler can help you with that, and now you can return non-optional images everywhere in your code, so you don't have to worry about forced unwrapping.

So this was a very specific approach that we're using in this unicorn browser app, but I want you to think about how you can use enumerations in your own code to provide other kinds of rich mappings. You can use more than just strings as well. You can use integers and even selectors. So there's a lot of opportunity to define these mappings in your own code.

Now, that was a deep dive into enumerations, but what I want to talk about now are segue identifiers, since this is something that you also use in your code all the time. Now, recently, my visions have been getting stronger, and I've had to develop an app that lets me actually keep track of unicorns and download them on the fly.

So I have a more complex application. And if we take a look at the storyboard, it ends up being pretty simple. I have a single view controller that can segue to two other view controllers. And for each of these view controllers, I've defined a segue identifier. What I want to do now is take a look and see what the code looks like when we override prepare for segue to configure the view controller that the unicorn browser view controller would present.

So we've overridden this method, and the classic way of implementing this would be something like this, where we switch on the segue identifier string. Now, you saw before I am using the same exact strings that I defined as my segues that I had in my storyboard, but the compiler doesn't know this. So when I switch on these two strings only, the compiler is going to tell me that this is not an exhaustive check, and so I have to add a default case because the compiler doesn't know that I have actually provided valid mappings.

But what happens if I were to add a new view controller and I have to -- I have a brand-new segue? How do I know where in my code that I actually need to change this logic? Well, let's take a look and see how we can solve this problem by using enums again. So I've defined a nested type on unicorn browser view controller which is going to represent the mapping between segue identifier cases and the string representation in our storyboard. So let's see how we can implement the prepare for segue method with more stronger typing.

So the first thing that I am going to do is grab the segue identifier string from the storyboard segue object and construct the segue identifier enum from that raw value. And I am going to provide a little bit of runtime checking debugging just in case I haven't provided a valid enum case for that new segue identifier.

Now, from there I can switch on the enum instead of the string. And that's really great because all I have to do now is switch on two cases, the compiler knows that I've only defined two cases in my enumeration, so that's all I have to switch on in my code.

Now, if we add a new segue identifier in our enumeration, the compiler is going to actually tell us that we don't have an exhaustive switch, and so everywhere in our code that we switch on this enumeration, the compiler is going to tell us exactly where we need to update our logic. Now, this is a huge benefit over the string solution.

So this is how you can override prepare for segue, but sometimes you need to invoke perform segue with identifier manually. And in our case, I might want to import a bunch of unicorns, download them, download images for them on the Web, and then present a new view controller.

So let's take a look and see how that code might look. So a classic solution to this would be to pass strings in the perform segue with identifier method. But we've already defined this enumeration. We really just want to use this mapping that we've already provided. So we want to use enums instead.

How do we do this? Well, it's actually pretty straightforward. We can define an overload on the UIKit define perform segue with identifier method that now takes enumeration instead of a string and then call the UIKit define method with the raw value of our enumeration. And if we go back to the code that calls this method with the enumeration, it works exactly like we want it.

So this is a really great solution specific to unicorn browser view controller. But what happens when we scale this up a bit? I want to take a look now at the structure of what we just did to see how we can apply this to more than just the unicorn browser view controller.

So what we've really done is provided a mapping between enum cases and their string representations in the storyboard. And we've also added implementation that uses that mapping to have a stronger -- a more strongly typed system in our application. But if we add a new view controller and we want to do the same thing, we are going to have to duplicate all of that work, and I don't want to do that.

What I really want to do is extract the implementation from the view controller and define a loose mapping for what that enumeration of segue identifiers is going to be specific to a view controller, and by doing that, we can use this implementation for many different kinds of view controllers, regardless of their class hierarchy.

So you can avoid a lot of awkward class hierarchies by reusing this implementation. And we are going to do this by using protocols. And so I have defined a new protocol. We are going to call this segue handler type. And this is what our view controllers are going to conform to.

And now we want to define the mapping that we just mentioned, and now it's going to be our segue identifier enumeration. And we want to make sure that the segue identifier conforms to the raw representable protocol. This protocol is an underlying implementation detail of how enumerations that are backed by other types work. And the compiler can synthesize this automatically for you.

So that's all we have for the protocol definition. What I want to do now is use a Swift 2.0 feature called constrained protocol extensions to actually add the implementation that's going to be our generic reusable code. So we are going to extend this segue handler type, and then we are going to constrain it, so we only want implementation to be added if these constraints are met.

And first constraint is that the type that conforms to this protocol is a UI view controller subclass, and this is going to now allow us to call UI view controller methods in our protocol extension. And the second thing is that we want to make sure the segue identifier mapping is between enum cases and strings.

Now that we've defined this constrained protocol extension, all we have to do is add our implementation. All we need to do is take the existing implementation of perform segue with identifier that we defined previously and slap the exact same implementation right into the code. And if we go back to our unicorn browser view controller, all we have to do is add this conformance to the new type, and we've already satisfied all the associated type constraints of the protocol because we already identified this segue identifier enum.

So if we go to our handle action method, we can call the code in the exact same way, but we are reusing the implementation in our segue handler type. So that was how you can reuse the perform segue with identifier method, but what about calling prepare for segue or handling prepare for segue? What I want to do now is define a convenience method that's going to take in a storyboard object and return a segue identifier enum, and this is going to be in our protocol extension.

All I need to do again is take the exact implementation we defined before and return the segue identifier enum that we created out of that code. And so if we take a look at our prepare for segue method, it ends up being really straightforward. All we have to do is switch on the result return by segue identifier for segue, which is the method that we just defined, and we only need to provide two cases that we are switching on the same exact way that we did before. But we have this generic solution. So let's talk about the benefits of this approach.

So the compiler knows exactly the segues that we've defined in our storyboard now when we add them to our segue identifier enum. So it can make sure we handle all of the possible cases in our code. We have a reusable solution by using this protocol extension, we can use this implementation on any view controller that conforms to this new protocol.

And we also have convenience syntax. We can use method syntax on these different -- on these different view controllers. It doesn't have to be free functions. So this is a really specific way that you can use protocols and constraint protocol extensions in this unicorn browser app, but you all have a lot of other interesting applications, and what I want you to do is think about how you can use protocols and associated type constraints in your own applications to encode some of the structure of your application to the compiler so it can help you with compile time safety.

I want you to also think about how you can use protocol extensions to share implementation throughout your application and avoid a bunch of awkward class hierarchy problems. So Ted and I spoke about a lot today, but what want you to get out of this is that the compiler is here to help you.

Ted spoke to you about how you can safely take advantage of new APIs. Now, this is largely done by the compiler, and so the compiler knows what is available and what is not for specific OS versions. And I spoke to you about strong typing in your applications and forcing application invariance and leveraging the compiler, letting it know what the constraints are of your application and encoding that information into your code so that the compiler can help you reason about problems at compile time and not at runtime.

So for more information, I suggest you go watch the Protocol-Oriented Programming in Swift session online, and if you want to have lucid dreams about Cocoa and Swift yourself, I suggest you go check out these two samples. The lister sample has an example of the segue handler type protocol, and DemoBots uses a bunch of interesting ways it use enumerations in protocols for compile time safety. Now for more information, you can check out the Swift Language Documentation. We have the Dev Forums, and you can contact Stefan if you have any questions. Thanks, everyone, and I am looking forward to seeing you at the labs.

[ Applause ]