Tools • iOS, OS X • 53:20
Explore the modern features of the Swift programming language. Learn about object initialization, closures, and optionals. See how you can perform pattern matching using Swift's powerful switch statements.
Speakers: Joe Groff, Brian Lanier
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
Good afternoon. Welcome. Welcome to "Intermediate Swift." My name is Brian Lanier. I'm an engineer in Developer Publications. Later I'll be joined on stage by my colleague Joe Groff, who is an engineer on the Swift compiler team. We're both really excited to be here today to talk with you more in depth about the Swift programming language.
I've been in the labs the last couple of days and I've been very impressed already with what you've been able to do with Swift, and I think that to go into some more detail on some really key features of Swift, you'll be able to take advantage of those features even more so in your code.
So, in particular, we're going to look at some features in some more detail, including optionals and how you can use them to make your code safer. We're going to talk to you about memory management in Swift and how it's largely automatic. We're also going to talk about initialization and how you can take advantage of the power of closures and pattern matching in your code.
So, I want to begin by looking at one of Swift's most powerful features, optionals. But first I want to take a look at why we might need optionals in the first place. Say you need to work with input data from a user. Here we're asking for the user's age. The response comes in as a string, but say we need to convert that string to an integer using this toInt method here.
Now when the user enters a string that represents a valid integer, the toInt method returns the correct result, but, however, because you're not in control of the user's response you have to deal with cases or situations where there's not an obvious value to return. The user has full control over what they're entering in. So, if they try to get cute or snippy, we don't really have a clear value to return in this case.
So, what do we do? What value do we return? Well, we have lots of options, right? Well, as you're probably aware, various sentinels have been used in various different languages to model these kinds of problems, but having so many sentinels around isn't very good. It's not very safe. It's a common source of bugs and problems in your code.
For one, you have to know which of these possible sentinels was chosen by the author of the API, and then you have to remember which one to check against it. So this isn't a very good pattern. So really how should we model this? Well, in Swift, we model problems like these using the optional type.
The optional type represents possibly missing values of any type whatsoever. The optional type has two discrete states: a default, nil state, which represents literally the absence of any value whatsoever, and optionals are defaulted to nil. You've probably seen a couple of examples and options. If you've seen the "Introduction to Swift" talk, you write them by writing the base height followed by a question mark to indicate their optionality.
Now, in Swift, nil is not like Swift in Objective-C, where in Objective-C Swift is an object pointer to nothing, and it only works for the reference types. In Swift, nil is a true sentinel value. It works with any type whatsoever. It literally just means there's no value present.
The other state of an option includes a presence of a value that's been wrapped up in the optional. Think of optionals as this wrapper container that wraps up values when there are values present. So here we're setting the value of optionalNumber to 6, and the value is wrapped up in the optional. We'll see how to unwrap this value and access it in just a little while.
So, now that we have a single sentinel value that works with any type at all, even integers, it's clear what this method should return. It's clear that whenever we have a value that's not valid, we should just return an optional, and in fact, the toInt method is defined in the standard library under string type, and it does just that, it returns nil. Now because we have optionals, we also have nonoptional types as well. So what are nonoptional types? Well, they're just ordinary types you would think they are. Integers, strings, even your custom objects.
And the great thing about nonoptional types is that they can't be nil. And we think this is pretty cool because this makes your code safe and predictable. When you declare a value of a nonoptional type, it can't be nil. You can be sure that it's there when you need it, and you can't be surprised by unexpected nil values propagating throughout your code.
So, now that we know what optional types are and what they represent, let's see how we can use them in our code, for example, to write a function that returns an optional type. So here we're going to write a function that looks for a string in an array of strings, and if that string has found the array it simply returns the index, but as you might guess, this is a case in which if the string isn't found we need some value to indicate that failure, and because we have nil and optionals at our disposal, let's go ahead and change return pipe here to an optional by adding the question mark at the end.
So, let's implement the function. The first thing we need to do is enumerate the array, and here we're using the enumerate method to find the standard library, which actually enumerates to an array and returns as a tuple, the index and the value of each value in the array.
We can use that in the 4N statement here and check to see if the string that we're looking for matches the value and, if so, simply return the integer. Now, we're returning an integer here, but then it gets wrapped up as we said in the optional return type. And because we have nil at our disposal, we know exactly what to do when we don't have a valid result. We just simply return nil.
Now, as I said, we need to unwrap optionals. So, let's use this function here to look through an array and try to find a name in this array. Here you can clearly see that the string that we're looking for is in the array, so we have a valid result to return.
Then we can check, though. We always want to check to make sure the value is present, so we can simply ask if the index value isn't nil here - and notice that I don't, I'm not checking explicitly against nil, and that's because optionals can be used in Boolean context. That means that you can check them directly, very naturally, like I've done here.
So if the value is present, which it is, we can simply try to use that value in the subscript of the array, but if we've done it like this we have a problem because the array is expecting a nonoptional integer, but we've said that the function returns an optional integer.
So the types don't match here and the compiler will let us know that first, before we use it, we need to unwrap the optionals. One way we can unwrap optionals, is using the force unwrapping operator which simply consists of an exclamation mark. You write the exclamation mark after the optional value that you wish to unwrap.
And here because our search returned a valid response, we can simply print out the result. But you have to be careful. As I said we should check to make sure the value is nil first before we unwrap it. So if we try to use the forced unwrapping operator here without checking, we would actually get a runtime assertion, a runtime error because we need to make sure the value is there, and we're trying to force it when it's not.
So instead in Swift we have a better way of doing this where we can actually test and unwrap at the same time using what we call optional binding. Optional binding uses this if let syntax you may have seen earlier in the "Introduction to Swift" talk. So, the if let statement, how does it work? Well, first as I said, it tests, so it tests to see the optional type here to see if it's actually present or if its value is nil, and if it's not nil, it assigns, unwraps and assigns a nonoptional type to the index value here, this temporary constant. And you can use a temporary constant because it's unwrapped as the index, as a subscript to this array, and everything should work just fine.
But we can actually combine these together. There's no need to actually include a temporary variable. We can simply evaluate the result of the function directly in line and then assign that after it's unwrapped to the index here, which now index is of type nonoptional Int, just regular old Int.
But that's what optional bind looks like in a very simple case. Let's look at how it might look if we did something a little bit more complex. So, to do that, let's set up a couple of classes. Say we have a person class, which has an optional residence, and a residence which has an optional address.
Finally, we have an address class that has three optional properties: the building number, a street name and apartment number, all of which are optional. Let's do a little bit more bookkeeping. Let's go ahead and create a person instance, Paul, here, and let's give Paul a residence, and let's also give Paul's residence an address so he can receive his mail.
Now because Paul doesn't live in an apartment, we're not going to set the apartment number, but we're going to set the building number to the string 243 and the street name to Main Street. Now let's say that we wanted to get at the address number as an integer, not as a string. Well we could use if let binding to drill down through all of these properties and sub properties of the classes we've defined.
So first we can test to see if Paul has a residence. If so, unwrap it, assign it to home. We can do the same with the address, and finally we can drill down and finally use the toInt method to convert that string into a number and assign it to the address variable.
But this is really cumbersome, as you can tell. This does not lead to pretty code, and it's kind of hard to follow all of the nested if let syntax and statements here. So in Swift we can actually use optional chaining as a way of accomplishing the same thing. Optional chaining lets you work with optionals to conditionally perform operations. It's like messaging nil in Objective-C, but in such a way that it works with any type whatsoever.
Optional chaining provides a clean and succinct syntax for doing these kinds of operations. So what we're doing here is we're just evaluating each point using the question mark operator or the chaining operator to see if the thing to its left, which is of an optional type, if it's nil or if it's present before we proceed to the next expression.
So to see how this works in a little bit more detail, let's look at this example and see how it's evaluated. You could think of the evaluation of an optional chaining expression like two parallel railroad tracks that end in two different destinations or two stations. The first station being the optional value that's wrapped up here, so in this case an Int that's wrapped in an optional, or simply nil when there's no value present.
So let's see what this looks like as we go through the expression. Well, Paul is nonoptional, so that's obvious. We've set him; he's a valid instance. We can go to the first optional here in the chain expression, residence. Well, we know that we've set it as a residence, but if it were nil, we would actually just take the nil track, and the entire expression would return nil. It's still of an optional type, but it simply returns nil, and the rest of the expression is ignored.
But of course, we did set a residence. And we can do the same here along the path. And we can check every single optional in a chained expression to see whether or not it's valid. In this case, we've set an address, and we've set a building number, and finally we come to the last method call after we've checked all of the rest of the optionals. Now the toInt method as you recall returns an optional type itself, so there's actually one more path we need to consider. It could, in fact, return nil if it can't convert the value.
But we've set it, of course, to a valid string representation of an integer, and so toInt method has a result to return, 243, and the entire expression is completely evaluated. It's wrapped back up in an optional so that the address number is now an optional with an underlying value of 243.
But because it's an optional value, if I need to use it as a real integer, I need to unwrap it still. Actually we can combine these operations together, optional chaining, and if let syntax or optional binding, to test and unwrap it at the same time again. So here we can take the entire optional chained expression, evaluate it, see if it isn't nil, unwrap it and assign it to the address variable. We can use it like an ordinary nonoptional type. For example, to add it to a database. So that's how optionals work.
[ Applause ]
It's pretty cool. So that's how optionals work. It's how optional chaining works, how optional binding works. I want to take a step back and show you what optionals look like under the hood. I don't want to spend a lot of time here, but I do want to show you how powerful and expressive the Swift programming language is that we can actually model something so fundamental to the language like optionals in the language itself.
So optionals are really just a simple enumeration, a generic enumeration at that, of any type. You can see the Some type here represents the case in which I said that there is a value present that's wrapped up. You can see that wrapping here going on. And the None case just simply is a default value, which we've said that you can indicate using nil. We haven't talked too much about generics, and we're not going to go into generics in much detail here, but I encourage you to watch the "Advanced Swift" talk, where you can see the full power of generics in action.
So that's optionals. You use optionals to work safely in your code with possibly missing values. Missing values are nil, and present values are wrapped up in the optionals. You can then unwrap the optionals in two ways: either with a forced-unwrapping operator, but only do that if you're sure there's a value there and it's not nil; and you can also use if let syntax or optional binding to unwrap and test at the same time in a very safe way. And finally, you can use optional chaining to work in a very succinct and eloquent way with multiple optional expressions chained together, and that's optionals. So, I'd like to invite Joe back on the stage to talk to you about memory management in Swift.
[ Applause ]
My name is Joe Groff. I work on the Swift Compiler. And I'm really excited because managing memory is my favorite thing to do when I program. No, it's not. Thankfully Swift is built on automatic reference counting, the same model we used in Objective-C or ARC. We don't make you use pointer syntax; we don't make you spell out Alec. But still we need a clear class like this and a constructed class, you get a memory allocation implicitly, and a reference to that is what gets stored in a local variable, and it's these references that keep that memory alive.
Now we can juggle a couple of these bowling pins here, and when the last reference to a class goes away, an object goes away, the object automatically gets deallocated. ARC is a horrible juggler, but it's great at reclaiming unused memory. When a reference goes out of scope, it's automatically released. But as long as you have some reference to the object, it's kept alive, and only when the final reference is released is the object deallocated. It's safe. It's predictable. It just works.
Except for those cycles. Sometimes it doesn't make sense for an object to be owned. For instance, apartments literally aren't owned by their tenants, but they also will have like multiple people living in them over the lifetime of the building. On the other hand, a person will move from apartment to apartment over their life. Even if you move a person into an apartment, this relationship isn't really an owning relationship. And there's a problem if we try to model it as such. Let's set up a dictionary of our renters and our apartments - say we're a landlord.
Let's move one of our tenants, Elsvette, into her apartment. Now there's a problem. If she tries to move out, she's trapped. The reference to the apartment keeps her object alive, and even worse, if we try to sell off the property we're stuck with the deed. Both objects keep each other alive in a reference cycle.
So like Objective-C, we have weak references, and these tell Swift that this object isn't responsible for keeping the object on the other end alive. Now, when we work through the example again, a weak reference between these two objects, Elsvette can move out of her apartment and make a clean break with her landlord. And the reference automatically gets reset to nil. There's no dangling reference to the allocated object.
Now of course, when we sell off the property, both objects end up deallocated, and there's no leak. And weak references in Swift are modeled as optional values. You can use all the optional operations that Brian just showed you to work with them. And when you take an optional weak value and you bind the nonoptional part out of it, you get a strong nonoptional value, and you can safely work with that just like any other reference.
If you're applying a single method or looking at the single property conditionally on a weak reference object, you can also use the chaining operator as a nice shorthand. Some things to be aware of are that the test and unwrap pattern does not work very well with weak. You could go to cash your tenant's rent check, and find out that it bounced, and evict them before you have a chance to even greet them, and end up with a runtime error you didn't expect. Chaining also doesn't preserve a strong reference if you use it to try to apply multiple methods to the same object. Here cashRentCheck could again cause the tenant to get released, and then we'd end up greeting a nil object, and then, in fact, not calling greet at all.
Weak references are great for breaking cycles, but they aren't always ideal. Let's say we're a modern landlord, and we accept credit card payments from our tenants. A dream, I know, but bear with me. Now a credit card should only ever be owned by one person, right? So we really want this to be an immutable binding, an immutable property of the credit card. We also don't even really want to track a credit card if it isn't owned by a person. It shouldn't be an optional property.
There's obviously a cycle here, and we could break that cycle with weak references, but that forces us into some unfortunate loosening of the model here. First of all, weak references have to be optional because they have to be resettable to nil. Second of all, they have to be mutable because they could be reset to nil at any time. And we don't really want this.
We could end up accidently reassigning a credit card or ending up with an orphan credit card just to deal with memory leaks. That would suck. So, instead, we have another kind of weak reference, the unowned reference. What this tells Swift is that although I don't have an owning stake in the object, my life depends on it. I cannot live without my owner, my card holder.
So we can go back to our renter's table. We can save our tenant's credit card information, and you can see here that the credit card is uniquely owned by the person record, so that when her record is deallocated, the entire object graph gets deallocated. There's no need to observe the credit card independent of the person. And because there's this assumption that the program is not correct without the object on the other side, you can use unknown references just like strong references. You can assign them into a local variable, or you can call methods on them directly.
It's a bit like unsafe, unretained in C, or Objective-C, but it's still safe. We still assert that the object is still allocated every time you access it through an unowned reference. So let's look at three kinds of references in Swift. Strong references are the default, and that's because they're what you should use most of the time. Most of the time you have an ownership stake in an object you want to use.
However, if you have independent objects, objects with independent lifetimes, sort of a, you know, a casual relationship going on where you can move on if they went away, weak references are great for modeling that sort of thing. If part of your program ends up deallocated due to maybe low memory, the rest of your program marches on, and optionals are a great way of dealing with weak referenced objects going away. And unowned references are great for back references from dependent objects backed up to their owners. If part of the object graph goes away, the entire object graph goes away, and there's no need to deal with nil.
And that's memory management with Swift. It's automatic, it's automatic reference counting, it's safe and it lets you think about the relationships between your objects, rather than the raw mechanics of memory management, making strong, weak and unowned references to model those relationships, and provides a nice, safe foundation for Swift. To talk a little bit more about that foundation, I'm going to bring it back to Brian to talk about initialization.
[ Applause ]
Thank you, Joe, thank you, Joe. So I want to talk to you about initialization in Swift. Initialization in Swift follows one very simple rule, and that's that every value must be initialized before it's used. This one simple rule is enforced by the compiler, and it ensures memory safety in your code.
It ensures that you never read from uninitialized memory, and it, therefore, helps eliminate an entire class of bugs. Let's look at how this rule applies throughout the language. So let's start with the simple variables. Swift doesn't default initialized variables, constants, or properties because there often isn't a natural and correct value for everything. The only exception to this is optionals, where we saw earlier that optionals have a very natural default value of nil.
This means that you need to set these values explicitly in your code, but it doesn't mean that you need to declare these values at the point that you declare your variable as constants or properties. It just means that you must do so before you try to read from them or access them.
For example, say we wanted to set message here to an appropriate value depending on whether or not a condition was true and then print that message out. Even though the condition is true in this case, I mean we're in the middle of the session after all, we'd get a compiler error because we haven't considered every possible branch in our code. We haven't said what the value of messages when the condition is false. So let's add an else clause to do just that.
Now when we read for message to print it out, we can be sure that it has an explicit value for every single condition in our code. But now as I'm sure you're all aware, it's quite easy to forget to check all of the conditions in your code. So rather than surprising you by unexpected and hard to track down behavior, the Swift compiler catches these kinds of mistakes for you and ensures that you're explicit about the values you want to set for every branch and under every condition in your code. This is great for memory safety. Now that we've seen how this rule applies to simple variables, let's see how it applies when you're defining your own classes like, classes or structures, your own types.
So, inside of the structure our class declaration initializers handle this responsibility. They handle the responsibility of satisfying the rule that everything is set before it's used. Now, you've seen some initializers in some of Joe's examples and memory management just a while ago, and they're declared using the key word init, and they're invoked at object creation, or instance creation here, using this initializer syntax.
Now initializers can have any number of labeled arguments or parameters, and when they do, you need to call, you need to call it using these label parameters. They're required at the call site. Before we look at initialization in classes, let's take a look at initialization and how it works in structures. Let's define a simple color structure that has three properties - red, green and blue to represent the color components - and we'll deal with alpha later. Now let's say that we want to create colors in different shades of gray.
Well to do so we can define a single initializer here, a very simple one, that simply has, takes a single grayScale double value and sets all of the stored properties of the structure to that same value. Now, if we had forgotten to set the red property, for example, we'd get a compiler error because we haven't satisfied the rule that every value must be initialized before it's used. The same is true for calling a method in your initializer before you set all of your properties. For example, say we want to validate, we want a method that validates the color components to make sure they're within a valid range of values say 0 to 1.0.
If we then call this method here inside of the initializer before we set all of our properties, we'd again get a compiler error because we're trying to call a method on self on the instance, but before self has been fully initialized. So if you need to access self just customize any property values or to call on the methods in your initializer, always do so after you've set all of your stored properties to appropriate values.
Now in structures, if you don't define any of your own initializers, Swift provides you with a memberwise initializer automatically. The memberwise initializer has arguments that correspond to each of that instances store, each of that classes or structures stored property. You can also provide default values in line directly for your stored properties, and when you've set all of your stored properties and you haven't defined any of your own custom initializers, Swift provides a default initializer for you to use that takes no parameters and creates an instance with the properties you set as default values when you declare your structure.
So that's how initialization works in structures. Now let's take a look at how it works in classes. Really it's no different. So let's start with a simple car class here that has a single property, a paint color, and a single initializer that just sets that paint color to an appropriate value. Now, in practice, classes and often subclass inherit from other classes. So we need to deal with how initialization works with subclasses as well.
So let's define a subclass called RaceCar, and it has one extra property and its own initializer. When the RaceCar's initializer is called, it first sets its own properties, and then it called the SuperClass initializer. And the SuperClass initializer, of course, sets its own properties to an appropriate value as well.
And only after the SuperClass and all the SuperClasses in the class hierarchy have had a chance to set their own values is a class fully initialized and ready to use. Now if you're coming from an Objective-C background, you probably noticed that this is opposite or different than what you've noticed, than what you do in Objective-C, where you always call your SuperClass first and then you set your own properties.
So in Swift if we were to do this, if we were to call our SuperClass initializer first and then set our own properties, we would actually get a compiler error because it's not always safe to do so in this order. And the reason Swift's initialization follows this order is that it ensures memory safety, so that you don't try to access memory before it's been completely initialized. Now it may not be obvious where you might run into problems by doing things in this order, so let's take a look at an example where calling your SuperClass initializer first is unsafe.
Let's say, for example, that we want to define a function or method on our car class that fills up the gas tank, and we want to call that function inside the initializer so that every time we get a new car it comes with a full gas tank. And because race cars use a different kind of gas than regular cars, we've overridden this method in our subclass.
So, when we create a new race car, and its initializer is called, it first called its SuperClass initializer, which then sets its paint color properties, and finally calls its fillGasTank method. But because we've overridden it in our subclass, it gets dynamically dispatched to the subclass's version of that same method, and that's where we have a problem.
And our problem is that we haven't got a chance in our own initializer to set our own properties, and so we're trying to call a method on self. We're trying to access this object or this instance that hasn't been fully initialized, and that's not safe. So in Swift, we always make sure we set our own properties before we call our SuperClass's initializer to have a chance to do the same.
So far we've just seen examples of a single initializer in a class, but you can have multiple initializers in your class just like you can in Objective-C. In fact, you can have designated initializers - what we've seen so far - designated initializers, which you may be familiar with coming from Objective-C, have the sole responsibility of creating an object and setting all of its properties.
And it has a job of calling the SuperClass's initializer and delegating up. Like you can think of them as funnel points that funnel the initialization process all the way to the class, to the BaseClass and hierarchy. But you can also have convenience initializers. Convenience initializers are sort of secondary initializers that provide an alternative implementation initialization interface that allows you to say, pass certain default values and make it easier for your clients to use.
They can only call across. They can only delegate side to side in a particular class. They never call up. So they can call designated initializers in the same class or other convenience initializers, but ultimately those convenience initializers need to call a designated to actually do the heavy lifting of setting up properties.
So let's see how you might write a convenience initializer in your own code. Say we have RaceCar again, and we want to create a convenience initializer that lets it just pass in a color, and it sets the turbo property to true, and it does so by delegating this task to the designated initializer in the same class. Now when we do this kind of thing in Swift, when we define a convenience initializer, we need to let the Swift compiler know by including the convenience keyword in front of the declaration.
We can even provide more than one convenience initializer, so we can even provide a convenience initializer that lets us create a RaceCar providing no parameters at all and then delegates this task, here providing a default gray value, and it calls the other convenience initializer we've just declared. And that in turn, of course, has to call the designated initializer to actually set the values. And because we have a SuperClass to deal with here, that initializer needs to call it SuperClass. And how are initializers inherited in Swift? Well, they're not inherited by default because doing so isn't always safe.
However, when your class provides no initializers at all, and you set default values for all of its stored properties as we've done here with the FormulaOne RaceCar, where we've set a single stored property to a minimum regulation weight, and since we know that weight we can set it directly. Now when we do this, we actually inherit automatically all of the initializers of the SuperClass. That includes designated initializers and the convenience initializers that we've declared.
Now, if we did this in this particular case, we'd have a problem because one of the convenience initializers we've inherited sets the wrong value for turbo. Formula One race cars aren't allowed to have a turbo by regulation, and so this wouldn't be appropriate in this case. So instead we'll define our own initializer here, and now it's a designated initializer because it calls super and passes that turbo value of false.
And because we've done this, we've provided our own initializer, we actually don't inherit those other convenience initializers or the other designated initializer. So that's how you would use initializers to set properties, and how you can set properties in line directly. But I haven't told you about a pattern, and you may be familiar with it from Objective-C, sometimes called lazy instantiation or lazy initialization, where you want to delay the evaluation of a property in setting it because, for example, maybe doing so is computationally expensive.
So we have the same concept in Swift using lazy properties. And let's take a look at an example of where you might use one, where it might be appropriate. Say we have a game class, and the game can either be a single player game or a multiplayer game. Now, the multiplayerManager, let's just suppose that it does a bunch of expensive computation, and so we don't want to create it if there's only ever a single player.
We only want to create it when there's multiple players. So we want to delay that initialization until it's appropriate. So we can do this in Swift by including the @lazy attribute in front of the declaration here, and when we do this, it will only be evaluated the time it's accessed and only when it's accessed and only that one time. Now just as we have initialization in Swift, we also have -
[ Applause ]
Just as we have initialization in Swift, we have deinitialization. Deinitialization is responsible for tearing down the object. Now as Joe has already told you, memory management in Swift is automatic, so most of the time you don't even need to think about the initialization - it just works, it just happens for you.
But for cases where you do need it, for example, to unregister yourself as an observer or, for example, to close a file that you may have opened, we want to have a chance to close that file and clean up those nonmemory resources. So for that we use a deinitializer, and deinitializers are just declared using the deinit key word.
So, for example, here we have a class that when we open up a file, if this class gets deallocated before we have a chance to close the file, that's not good. So think of this as the last resort, the last chance you have to clean up nonmemory resources before your object is deallocated. Again, most of the time you probably won't need them. So that's initialization in Swift. It's safe.
You initialize all of your values before you use them, set your stored property values first and then call your SuperClass initializer, always in that order. Designated initializers only delegate up, and convenience initializers only delegate across. And finally, we have the initializers if you need them, but most of the time you won't. So that's initialization. I'd like to invite Joe back on the stage to talk to you about closures in Swift.
[ Applause ]
Now, there are a lot of ways to sort an array, and we could keep our standard library writers busy writing a million different sort methods on array, but we'd rather not. So instead we have a single sort method that takes a closure. You've probably seen a bit of this in the intro talk or even in the keynotes.
A closure in Swift is spelled inside curly braces. You follow it with a signature. In the case of sort, it takes two arguments of the element type of the array, in this case String. And in terms of Boolean, telling us whether the elements are sorted in the right order.
Default with the in keyword - you're binding these arguments inside the body of the closure - and then you follow it with the body of the function. And now we have a single sort method that can sort an array in a number of different ways. We can sort A to Z ascending, using the lesson operator. We can sort descending, using the greater than operator. We can even do something like sort from shortest to longest by counting the number of characters in each string. Now this is a great interface. It's also a lot of typing.
And closures in this form are a little awkward to work with. And the actual interesting part gets lost in a lot of syntactic noise. Thankfully we have type inference to help us here. Let's open a definition of sorts. Sort, of course, already knows what kind of closure it takes. In this case, we're working with an array of strings so it takes two strings and returns a Bool. Let's look at the call side again, and that's really offensive, that's really burning my eyes here and I want to do something about that.
Much better. We can infer the argument and return types from the signature of the function. That's a great improvement, but we don't stop there. If a closure consists of a single return statement like this, we don't need to specify return. A single expression closure implicitly returns its results.
[ Applause ]
Thank you. At this point even the argument list is starting to look a little heavy. Let's get rid of that, too [laughter]. There are implicit argument names - $0, $1, $ a million. They may need a little taste in some cases, but they're there if you need them and they make closures really succinct and easy to use.
We've also seen that we have trailing closure syntax. When you have a single argument like this, you can move it outside, and when it's the only argument you can drop the parens altogether. And this makes functional programming in Swift really expressive and fun to do, and we've added methods to the array class, to the array struct, to make this possible.
I'll start with a list of all the words in the English language that we got from the dictionary service. Now we can pick out words that share some common traits, say the ones that end in G-R-Y, and it turns out there's only two, and we can use the filter method which selects out, which applies a closure to each element of the array and for the one that it returns true returns a new array containing those. We can then transform these with the map method, which applies closure to each element and collects the results into another array. And then we can use the reduced method to fold these into a single string.
Now each of these is chaining another method call onto the result of the previous expression. Even if we're using trailing closure syntax we can just chain method invocations using dot, like this. And because it's a single expression, we can even include it in a yet larger expression and include a lot of complex logic into a very small amount of code. It's an extremely expressive way to build up your logic, even for a code that doesn't involve Hulk.
[ Applause ]
Closures can also capture local states. We could sum up all the elements in array using a for loop or reduce, but we can actually abuse math to do it. Inside of closure, we can refer to local variables and we can even mutate them. There's never a need to mark them under under block or in any way. It just works. And then we can return that result.
Closures are really just literal functions in Swift. Just like you can pass a literal integer or a named integer constant as an integer parameter, you can pass a closure or, indeed, a named function as a closure value. We don't have to wrap up print line in a closure like this.
We can just pass print line. There's only one kind of function type in Swift. We can even do this with bound methods. Instead of packing an index set like this, wrapping a call to the method in a closure, we can just pass the bound method directly as a function value.
Closures in Swift are just ARC objects. They follow the same memory management rules as classes. So if we wanted to have a global variable that we can install a call back on and write a function that installs a call back using local state of that function, that reference will keep that closure and all of its local state alive.
There's never a need to explicitly copy a closure or to worry about dangling references to its environments. And just like closures of functions, functions are also closures. We can nest functions inside other functions and refer to the local state of the outer function. If we look at these in the debugger or in instruments, they show up with their names.
So when we store a reference to this in a global variable, even when the local function goes out of scope, that reference keeps the local function alive, just like the closure. Now because closures are ARC objects, they have the same ownership and reference cycle problems as classes can have. If we did something as seemingly innocent as taking this call back and putting it into the property of a class, we'll have a problem.
When we go to set up these objects - of course, what we're really doing is we're capturing self, and we're capturing it as a strong reference. And then when we store reference to the closure inside the class, we end up with the reference cycle and a memory leak. The compiler actually won't let you implicitly reference self inside of closure because of this problem. Yeah, it's pretty nice.
Now in a past life, you may have solved this using unknown reference, using a local variable with weak ownership. In Swift, we can use unowned ownership instead, but this is problematic. Even if we did this, if someone cut and pasted code into this closure, it would still be capturing self strongly. So we have a better way to do it in Swift. You can specify directly inside the closure how to capture its local states.
[ Applause ]
And now when we set up the object graph, there's no longer a cycle, and there's no leak. So that's closures in Swift. They have an incredibly expressive syntax and a much simplified memory model from Objective-C, and they make functional programming in Swift really awesome and powerful. Now I'd like to talk about another powerful feature of Swift, pattern matching. You may have seen in the intro talk that Switch not only works with integers, it also works with strings or, indeed, values of any type, and also works with ranges of values, but that's just the tip of the iceberg.
You may also remember that enumerations in Swift can carry associated data, and we can tell not only that a train is delayed, we can say by how much, with an integer value that only makes sense when the train is delayed. Because this associated data is tied to that case of the enum, we access it through a case statement.
We combine it to a variable and then use that variable inside that case statement. It's electrically scoped to that case. Cases are electrical scopes by default in Swift, and we can't access it anywhere else. It's much safer and easier than using an enum with a union or struct like you may have in C. Now this is a simple case of a pattern.
This outer construct Delayed only matches values of the delayed state inside the enum. And if it is at the delayed state, it unwraps the associated data and passes it down to a sub pattern. In this case, the let pattern binds it to a variable. This is actually a completely independent and fully powerful pattern. Anything we do at the top level of a switch with an int we can do to its associated value.
So we can match it against a specific value, say we're delayed by one minute. We can match it against a range of values say we're delayed from two to 10 minutes. We can also ignore the value altogether, match any Delay using the underscore, which is the wild card pattern.
[ Applause ]
You can also use enums as associated data of other enums. We can track not only the status of our train while we're traveling, we can track the entire state of our vacation, and when we do so, we can tell our friends on social media based on that state.
We can match a simple case of the enum, we can match a nested associated value two levels deep. If we're 15 minutes in, we might be able to, 15 minutes delayed, we might be a little snarky, but still composed. If we're delayed any more than that, we might get a little upset.
Pattern matching doesn't just work with enumerations. It also works with the dynamic types of classes. Let's say we're going to tune up a car, and it's an arbitrary car. If someone brings a Formula One car into our mechanics, to our mechanic, he's probably going to be a little confused. He might want to take it to a specialist.
We can do this using the as pattern. This will both check that the value is of a type, and if so, cast it and pass it into the sub pattern, which we can then use to bind a variable. And then we can pass it on to a pit crew, who will then tune up that Formula One car. For a more mundane car, we can pattern match it, and then if it has a turbo, tune up the turbo before following through using the fall through statement into the default case that does the normal tune-up for a car.
We can also pattern match multiple values simultaneously using tuples. Tuples are a great way of returning multiple values from a function or for combining related values like the components of a color, and when we do so, each element of this tuple pattern is an independent pattern. We can match red against a single value, we can match green against a range of values, bind blue to a variable and ignore the alpha component all in a single pattern.
[ Applause ]
We can also match multiple values and test additional conditions with those values using a where clause. Here we're testing that they're all equal to see if it's a grey scale value. So these are some neat parlor tricks. Let's try something a little more real. Let's validate a property list, a dictionary of arbitrary values of unknown type, and given a valid one. we want to return a well-typed struct.
Given a property list with a name key that's a string, a population that's a number and a two-letter postal abbreviation, we want to get a record like this. However, if one of the fields is of the wrong type, or if the postal abbreviation is too long, we want to return nil.
Let's see how we can do this with pattern matching. We'll start with a single key of the property list. And you may remember that optional is just an enumeration, so we can use an enumeration pattern to both reject property lists that don't have the key and unwrap the value for ones that do.
Inside that pattern, we can use a type pattern to both reject property lists that don't have a string name and get the string value out for those that do. Then we can use a let pattern to bind to that string value and store it to a variable with a single default clause handle all invalid names. Now we can repeat this for each element that we care about inside the dictionary, but there's a better way. Remember we can use tuple patterns to match these simultaneously and repeat this pattern for each key of the property list that we're interested in.
We can see that the name is a string, the population is a number and the abbreviation is a string all simultaneously, and with the where clause even check the abbreviations of the appropriate length, and we're done. With a single default clause, we handle all invalid property lists, and it all fits nicely on a single slide. That's the power of pattern matching.
[ Applause ]
It's an incredible way to test the structure of values and really improve the readability and safety of your code. So we still just scratched the surface of what Swift can do. We've looked at how optionals allow you to write safe code; we looked at the foundations of Swift in memory management and initialization. We looked at how powerful closure is and pattern matching are.
There's a ton more information online. There's the book that you probably already downloaded and read by now. There's also some additional material on the developers site about Interop with Cocoa. If you're following along from home, talk to Dave, he's great, and there's our forums. You also might want to save your seat here. We've got a lot of other great Swift sessions right here in Presidio. Thank you.