Developer Tools • iOS, macOS, tvOS, watchOS • 51:46
Updated to include the latest features, syntax and best practices for Swift 3, this session introduces the basics of the Swift programming language. Learn the recommended manner in which to declare variables, define functions, work with fundamental value types, protocols, generics, and more. Explore some of the great features that make Swift a safe, fast, and expressive language.
Speakers: Dave Addey, Brian Lanier, Alex Martini
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
Hello, and welcome to Getting Started with Swift. I'm Dave Addey, and together with my colleagues Brian and Alex, I'd like to give you a quick overview of the Swift Programming Language. In the next 60 minutes you'll learn enough about Swift 3 to be able to read Swift code and hopefully to start writing it, too.
So let's start by taking a look at some of the basics of the language, and here's some code I wrote two years ago. I've written a constant, indicated with let, it's called language, and it's a string. And this colon in the middle, you'll see this often in Swift, this means is of type. So language is of type string, and I've just given it a value of Swift, using a string literal in my code. Let's make a few more. Let's have an integer called introduced. We'll that to 2014. And a Boolean called isAwesome. And we'll set that to true.
Now, you might notice that none of these values need to change. The language's name isn't going to change, nor is the year it was introduced. And two years on, Swift is still awesome, so that can probably be a constant as well. And this is the general principle in Swift. If something doesn't need to change, we declare it as a constant. Now, you might also notice a naming trend here. Constants and variables tend to be written as lower camel case, and types, such as String, Ints and Bool, is upper camel case.
Now, if we look at the things on the right here, it's pretty obvious, actually, that what I want is a string, an integer and a Boolean, and where this is the case Swift can infer the type for us. We don't actually need to write it ourselves. So you still get things that are explicitly typed, but you don't have to write as much code.
So that's some constants. What about variables? Things do sometimes need to change. So here's a variable indicated with var, and it's for the version of the language. This has changed. So let's bring it up to date to 2016. Now, if I try to do this for a constant, if I try to set isAwesome to false, Swift would tell me that it was an error, and quite right, too.
So a common thing to want to do in programming is to build strings from other values. We could do this by concatenating strings together, as shown here, but Swift has a neater way to do this, known as string interpolation, and this is how it looks. And we can put strings and other values inside a larger string by wrapping them in parentheses preceded by a backslash.
Here we're making the message, Hello WWDC. We're not just limited to strings, however. We can add other values as well, such as integers here putting the year in. And we could even add expressions as well. We can add year + 1 for some code that would work for a future year.
Now, strings in Swift are fully Unicode friendly. They're just as happy with ASCII as they are with dog, cows or with flags. And the same also goes for any constants and variables you create. You can use pretty much any character in the name of your constants and variables. Talking of [inaudible] characters, Swift does all the hard work of working out what it means to be an individual character, regardless of how your string is encoded behind the scenes or how it appears on screen.
So here I have a string called dogString. You might think that this has six characters in it. D, O, G, ?, !, dog face. But you would be wrong. There are five characters here, not six. This is a special character called question exclamation mark, which is really, really good for expressing incredulity about dogs. Now, if you don't believe me, let's get Swift to prove it.
Every string has a property called characters. It gives us a collection of the characters in the string and we can access the count property of that collection to see how many there are. If we pass that to the print function, we find out that we do, indeed, have five characters. But if you still don't believe me, well, let's iterate over each of those characters in turn, using the for-in loop and print each one of them on its own line, and you can see we do, indeed, have five characters; D, O, G, ?! and dog face.
Now, in addition to all of these fundamental types we also have some built-in collections. We have array and dictionary. You might know dictionary as a hash or map table from other languages. And the easiest way to create an array or a dictionary is with a literal. This is an array literal written with square bracketry on the outside and commas between each value. Here we have an array of four names, four strings. Dictionaries look very similar. Their keys and values are separated with a colon. Here we have a dictionary whose keys are strings, the names, and whose values are integers.
Now, you might notice from the things in this array that they're all the same type; they're all strings. It wouldn't make sense to insert something else in a names array, to have an integer or a Boolean or a bicycle. That would be just weird. For names we always want them to be strings, and in Swift we can express this.
We can say we want an array of strings, so we know as soon as we get anything out of it, we can do string like things with it. So this is how we write that. This is how we write an array of strings, a string with square brackets around it.
But as we saw earlier on, when it's clear from the right hand side what kind of type we want, Swift works it out for us, and that happens here as well. We actually don't need to write the type. It's clear we want an array of strings. The same goes for our dictionary. Here it's clear we want string keys and integer values, so Swift can infer that for us as well, but everything is still clearly typed.
Swift has all of the loops you might know from other languages. We have a while loop that checks its condition before first executing its body. And a repeat-while loop, which executes its body once before checking a condition to see if it should continue. And as we saw earlier, we have the for in loop, which we used to iterate over the characters of a string. But that's not the only thing the for in can be used with.
We can use it to iterate over a range. Here we have a range that runs through from 1 through 5 and includes most of those numbers, which we're using to print five entries from the four times table. We write this as 1...5. That is called a closed range because it includes both of those numbers. However, sometimes it's useful to have a range that runs up to one less than its final number. Here's an example of where that's useful.
I have an array of integers, but I only want to print the first five. Now, because array indices start from 0, I actually want indices 0 through 4. So for that we can use the half-closed range operator, ..< because it runs the one less than the final number, in this case 5.
We can use a for-in loop with an array. Here we're printing a message for each of the names in our array. And we can also use it with a dictionary. Now, note here that we're iterating over the key and the value, the name and the age, at the same time. And this is an example of Swift speech known as a tuple, which lets you combine those multiple values together and use them at the same time. And we'll see another example of this later on. And it makes for much clearer code when iterating over a dictionary.
So how would we modify these collections? Well, here's my packing list for WWDC this year. I've declared it as a variable so I can change it, but I've included the essentials, socks and shoes. Now, at WWDC 2014 I forgot to pack my socks and it was a nightmare, so I'm not making that mistake again.
So let's check that the first item in this array is definitely socks. After 2014, if I put it on the list, it will be first. We do this using a subscript, writing an index inside square brackets after the array name, and if we print this value, I have indeed remembered to add socks.
Socks and shoes will not be enough, however, for a week of the conference. I'll need more things. So let's append a new item. Let's append some trousers to this array as well, which we do using the append method. But there's a problem here. The conference is in America and they don't call them trousers, they call them pants, and that's going to cause all kinds of confusion. So let's change a value in our array. Let's change this to be jeans instead. Again, we use a subscript to do so, to change the item in index two. Jeans are called the same thing everywhere, so this shouldn't cause confusion.
Now, the conference is in California where it is always hot and sunny, so let's add a few more items. Let's add some shorts, some sandals and some sunblock. We can do this using the append contents of method and pass in a compatible array, another array of strings here, and they all get appended at the same time.
That said, whilst the conference is in California, it's in San Francisco, where it is not always hot and sunny. So maybe let's change those three items and replace them with a hoodie and a scarf instead. We can do this by passing a range into the subscript. And note that we're changing three items with two, this is still perfectly valid in Swift.
So what about a dictionary? Let's modify a dictionary. Well, here I have my ages dictionary from before. I'd like to add somebody else to it, and I do this just by adding a value for a key that's not already there using a subscript. Here I've added Justyn. But thinking about it, it was Justyn's birthday last week so this value is now incorrect. I need to update it, which I do in exactly the same way. I just assign a different value for the same key and now my dictionary's correct.
What if I want to retrieve a value from the dictionary? What if I want to see if we have an age for Devon or for Daryl, or perhaps for Daniel? Well, there might be a value in the dictionary for these people, but there might not, and we need a way to model that, and this is a great use case for a Swift feature known as Optionals. If we tried this for Amy, we might expect a value of 40 perhaps. But if we tried it for Daryl, what should we get then? There's no value here for Daryl.
Think of it like this. There either is a value in the dictionary and it's an Int, that's for Amy, or there's no value, there's no Int, as in the case of Daryl. So what we have here is an optional Int, which we write as Int question mark. And the question mark means there might be a value here, but there might not.
Now, we can check to see if we found a value by comparing it against nil. Nil is just a special way, a shorthand way of writing no value. If we try this for Daryl, there is no value, we'd print the message. Age not found. If we try this for Amy, well, we do find a value so it doesn't equal nil and so we don't print the message.
Typically, however, we don't just want to check for a value. We actually want to use it if it exists, and Swift has a really good shorthand for writing this, which we write as if let. Now, this says here the dictionary contains a value for Amy, let a new constant called age equal that value. And then we can use that value inside the if statement. And note that we use it as an actual integer. It's not optional anymore. The if statements checks for the value, unwraps it and gives us back a normal integer that we can do integer like things with.
So we've seen a few if statements so far. Here's another that prints a birthday message. There are two things to note about this statement. Firstly, we don't need parentheses around the conditions so we can just write them as they are. And secondly, we do add curly braces around each part of the statement to make it explicit which bits of code are going to be run.
In addition to if, we also have a switch statement, and this is useful for writing maybe more complex, more powerful matches. This switches over the current value of a constant or a variable and matches certain cases that can occur. So, for example, we can have the case here for an age where the value is one and we want to print a simple happy first birthday message. We can also match against ranges. Here matching any value that would make someone a teenager.
And we can match more complex patterns, such as this one, which says that a temporary constant called decade equal the value that we're currently matching, check if it will divide cleanly by 10, and if so, use it to print a special birthday message for anyone who's just turned 30 or 40 or some other significant age. But there's a problem with this statement as it stands. What would happen if we wanted to print a message for someone who was 41, or 97 or 56? Well, they wouldn't get a message, and that seems really unfortunate, especially on their birthday.
Frankly, Swift helps us out here. Swift makes sure that every switch statement is complete, that you don't accidentally forget one of the cases that you might need. And in this case we can add a default statement which catches every other case we hadn't already caught above and just says, Happy plain old boring birthday.
Here's another switch statement. This one takes a string value, user name, and a Boolean value that indicates whether that user's password is valid, and we'll use both of these values together to work out an appropriate message to display when this user tries to log into a restricted area.
And to do this we can switch over both values at the same time using a tuple, same as we did earlier on. So this means we can write some really, really interesting use cases, some really interesting switch cases. We can have the case where the user name is admin and the password is true, and print a message to welcome back our administrator. Now, in the case of a guest we never want to allow the guest into the restricted area even if their password is valid, and so we can ignore the password by writing an underscore, and this means just match any possible value here.
For all other users we actually don't care what the user name is. We just care about the password. So we can ignore the user name again, and instead, we've switched on what we want to do with the password's validity. To do this we create a temporary constant called IsValid, and we then use the ternary conditional operator, that's the question mark and colon here, to say if it's valid, use this message; otherwise, use this message.
So let's run that through for a few examples. If we take our administrator, the password's valid, they get a special administrator message, as expected. If we try this for a guest, well, even though their password is valid, they get the I'm sorry, you can't come in message. If we try it for Bob, his password is valid, he gets the welcome message as expected. But if his password is not valid, he gets access denied.
Now, there is one final thing of note about this switch statement, and that's that it doesn't have a default condition. And the reason it doesn't is it doesn't need one. It's already complete. If we take a look at the final case here, this actually covers all of the possible conditions that we haven't already matched above and so the switch statement is complete without needing a default. So those are some of the basics of the Swift language. I'd now like to hand over to my colleague Brian to introduce you to functions and closures in Swift.
[ Applause ]
All right. Thanks, Dave, as I was saying. Let's get started by looking at how you define a function in Swift. You define a function using the func keyword and you implement it inside of the curly braces. Here we've defined a simple function called sendMessage that prints a message to the console.
And you call this message in an intuitive way by writing its name, sendMessage, followed by an empty pair of parentheses. So let's add a parameter to the function that indicates whether the message should be sent shouting. You write the parameter's name, followed by colon and the parameter's type, just like you do when you declare a constant or a variable. Here we've added a parameter called shouting, which is of type Bool. And when you call the function, the parameter's name provides a label for the argument you pass in.
Labeling your arguments makes your code read better and makes the purpose or intent of each argument clear. In this case, if you left out the shouting label, someone reading your code later might think that true just indicated whether the message should be sent at all. So you can shout a message, but who are you going to send the message to. Let's add another parameter that lets us address the message to someone in particular. So here we've added a parameter called recipient, which is of type string, and we've included the recipient's name in our message using string interpolation.
And now when you call the function, you can pass in the recipient's name. Now, although the message prints as you'd expect, it doesn't read very well when you call it. sendMessage recipient Morgan is pretty awkward. When you call a function, you want it to read naturally. In this case you'd like it to say something like sendMessage to Morgan, which forms a nice grammatical phrase.
And you could do this by changing the name of the parameter, which then changes the name of the argument label, and this does make the function read better when you call it, sendMessage to Morgan, but it doesn't work so well inside the body of the function. Inside the body you really want a noun for the parameter, not a preposition, hey there to, isn't so great.
But sometimes there isn't a single word that works well both inside the function's body and as a label when you call the function. In Swift you don't have to give one of these up. When a parameter's name isn't appropriate as a label for the argument when you call it, you can explicitly provide a more suitable one.
You write the explicit argument label in front of the parameter's name. Here we've added to as an explicit argument label in addition to the recipient parameter, and this just means that you can use to when you call the function, sendMessage to Morgan, and you can still use recipient inside the body of the function. Let's add one more parameter to the function which lets you provide a custom message when you call it. Here we've added a message parameter of type string. Now when you call the function, you can pass on your own message.
Now, once again, this code works as expected, but it doesn't read very well either. sendMessage message is redundant. The message label isn't helping to clarify the role of the first argument because it's already clear from the base name of the function, sendMessage. Here the argument label actually makes the code harder to read. In the cases like these, you can write an underscore in front of the parameter's name. And this means that you don't provide a label for the argument when you call the function.
And now our function reads naturally when you call it; sendMessage, See you at the bash, to Morgan. Now, it's not very often that we need to shout our message, so we usually pass in false for that argument, and in Swift you can actually capture this behavior right in the declaration of the function.
Whenever a parameter has a single commonly used value, you can provide that value as the default, and you do this by assigning a default value, in this case false, right after the type of the parameter, and now when you call the function, you can leave out the corresponding argument and the default value is used.
And when you're deciding whether to use or when to use an explicit argument label, when to omit one or when to provide default values for parameters, remember that functions are declared only once, but they're used repeatedly. So the most important thing is that the function reads clearly and concisely when it's called.
Now, to learn much more about the kinds of things you should consider when you're writing great Swift API, check out the Swift API Guidelines talk. So we've looked at lots of ways functions can take values. Let's take a look at some of the ways they can return values.
Let's say you want to write a function that returns a first string in an array that has a given prefix. The function takes a string prefix, an array of strings, and it returns the string with a given prefix. As you can see, you use an arrow to indicate what type of values your function can return. In this case a string. So let's see how it works.
First use a for-in loop to iterate through each string in the array and then you check whether a string has a given prefix. You need to check whether the string has a given prefix using strings.hasprefix method. If it does, you're done and you can simply return the string by writing it in a return statement.
But what do you return if the array doesn't contain the string you're looking for? Well, because this function was declared to return a string, the only option you have is some valid string value, in this case the empty string. But this is not good Swift code. However, as Dave showed earlier, optionals are perfect for representing values that may be missing.
So you just need to change the return type of this function to be an optional string by writing a question mark after string, and now you can return nil when the string isn't found. And because the function returns an optional string, you can call it safely in an if-let statement.
Now, notice that Swift infers a return type of the function, so there's no need to write it explicitly here. So we've looked at some of the ways functions can take and return values of various types, such as strings, integers, arrays and even optional types. Let's take a look at writing one more kind of function.
Let's say you want to write a function that filters an array of numbers based on some condition. Let's think about what this function needs to do. It's going to take in an array of numbers and for each number in the array it's going to determine whether to include that number in the filtered results.
For example, if you wanted an array that's been filtered to contain only the even numbers here, you could test whether each number is divisible by two. In this case, of course, four is even, so it's included in the result array. If a number isn't even, such as 17, the filtering function just moves on to the next number to test, and so on through the rest of the array. Now, let's look at writing this function in code.
The function declaration is what you might expect, but what type do you put in for the includeNumber parameter? As you saw a few moments ago, deciding whether to include a number in the filtered result is itself actually a function, not just a simple value, like a string or a number, and in Swift functions can take other functions as parameters. So what does the type of a function look like. Well, all function types in Swift have this basic form. The type of the function's parameters, if any, go inside of the parentheses, followed by an arrow and the function's return type.
So, for example, here's our basic send message function from earlier. It has the type, empty parentheses because it doesn't take any parameters, arrow Void. Here void just means it doesn't return any values. And in Swift if your function doesn't return anything, you don't need to write the arrow Void explicitly.
And here's the firstString function. Its signature is a little more complicated, but its type follows the same basic form. It takes a string, an array of strings and it returns an optional string. So now that you've seen this syntax for function types, it's pretty clear how you need to finish the declaration for filterInts. The includeNumber parameter can be any function that takes an integer and returns a Boolean, and you write that type as Int in parentheses, arrow Bool. So let's go ahead and write the rest of this function.
You need to build up an array of filtered numbers, so here's a variable called result. It's been initialized to an empty array of Ints. And as you iterate through the array of numbers, pass into the function, you need to check whether each number should be included in the result array.
To do that you pass each number to the includeNumber function, and notice that inside the body of filterInts, the includeNumber parameter is treated as a name of the function it's passed in, and that just means that you can call it in the same way that you do any other function.
So that's how you write a function that takes another function as a parameter. But how do you call one of these kind of functions? Well, first you're going to need some values to pass into filterInts. So here's an array of numbers and a simple function called divisibleByTwo that indicates whether an integer is even.
As you can see, the type of divisibleByTwo matches exactly the type of the includeNumber parameter. So that means that we can pass the divisibleByTwo function as an argument to filterInts. You do this by simply passing in the name of the divisibleByTwo function and now you've got an array of only the even numbers. Now, notice that you don't include the parentheses when you pass in the divisibleByTwo function, and that's because you're not calling it at this point. Instead, it's called later inside the body of filterInts.
Also notice that we passed in the array of numbers by name, but you could have passed in a literal array instead; for instance, if you just needed to pass in a few values without needing to create a constant for later reuse. The same is true for passing in functions as arguments.
It's not very likely that the divisibleByTwo function will find much reuse. And it would be pretty cumbersome to have to make a new function every time you wanted to pass in a different filterInt condition. And if you look at the important parts of divisibleByTwo, you can see that giving it a name at all is really just a convenience geared toward reuse. divisibleByTwo is just a name for the functionality that you see highlighted.
And in Swift, just as you can write a literal string or a literal array, you can write a literal function without a name called a closure expression and pass it around in your code. The syntax for writing a closure expression is very similar to a function declaration, except that it doesn't have a name.
You write the entire body of the closure inside of the curly braces and you separate the closure's body from its signature using the Int keyword, as in use the parameter number of type Int in the body of this closure, which returns a Bool. Now, this is the most explicit or complete way to write a closure, but as you've seen before, Swift can infer a lot of information from the context your code appears in.
For example, the function type of the closure is already known from the type of the includeNumber parameter, and this means you don't need to write it explicitly. And when the entire body of the closure is a single return statement, as it is here, you don't need to write the return keyword either. So this is much cleaner syntax, but because the closure's so short, even the number parameter seems a bit redundant. And in cases like these Swift provides implicit argument names, so there's no need to even write the parameter name or the Int keyword.
These implicit arguments start with dollar sign and a number beginning at 0. So $0 refers to the first argument to the closure, $1 the second, and so on. And even though these are available to use in any closure expression, use them when doing so doesn't make your code harder to read.
Now, you've seen that Swift provides lots of convenient ways for writing closures, but passing them directly inside a function's parentheses is a little punctuation heavy. A closing curly brace right next to a closing paren is pretty terrible. However, when the closure is the last argument to the function, as it is in this case, you can write it instead as a trailing closure right outside of the parentheses.
And as you'll see later, if the closure's the only argument to a function, you can drop the parentheses altogether. Now, trailing closures are especially nice when the closure contains more than one line of code. For example, here's a more complex closure that determines whether the sum of a number's digits is even. Trailing closure syntax ensures that even complex closures with multiline functionality read naturally and elegantly.
Now, the filterInts function is pretty useful if all you want to do is filter integers, but what if you want to filter arrays of other types, like strings. For example, say you have an array of names and you want a filtered array that contains only the names that have less than a certain number of characters.
You could do this by writing a filter string function like the one you see here, and since you've already written a filterInts function, let's see whether you can reuse any of that logic. Well, first, you'd obviously want to do some bookkeeping and change all the places that refer to numbers to refer to strings. And next you need to change all of the Int types to be string types.
And because the behavior of these two functions is actually the same for both integers and strings, there's really nothing else to do. They're essentially the same function. And now you have this function that filters an array of strings. Now, if you want a filtered function that works for other kinds of types, you can repeat this process over and over and over again for each kind of type, but this would obviously get boring pretty quickly.
So instead, you can write one function that works with arrays of any kind of type. This is called a generic function. So let's look at how you write one. Writing one is not much different than what you've seen already. First you change the names to be more generic. So, for example, the function's name is simply filter, rather than filterInts or filterStrings.
Next, and more importantly, you need to fill in the placeholders that you see here with the type of the elements, but you don't know what that type is yet. So instead, what you really want is a type parameter. The actual type is determined when the function is called.
Here we filled in the placeholders with a generic type parameter called element. And so that you can tell the difference between type parameters and actual pre existing types like strings or integers, you write the type parameters up front inside of the angle brackets right after the function's name. And now you have a generic filter function that works with an array that contains any type of values.
You call this kind of function in the same way as before. You don't need to specify the type you want the function to work with because it's all inferred from the values you pass in. Filtering is such a useful operation that the Swift standard library has a filter method that works with any kind of sequence or collection. It works in basically the same way as the filter function you just saw, except that it's a method so you call it using dot notation, names.filter, as you see here.
The Swift standard library actually has lots of other methods that take closures as arguments. And another really useful one is the map method, and it also works with any sequence or collection. Map returns a new array that contains the result of a plan enclosure to each element. So here's an example that calls map on the array of short names from above. It passes a closure that simply upper cases each string.
You can even chain these together to perform multiple operations. So, for example, you can perform the filtering and the upper casing in one expression. By chaining these two method calls together using dot notation. The filtering happens first, and then map is called on the filtered array, which then finally returns the upper cased strings.
And this reads nicely even when you're using the trailing closure syntax. So the combination of closures and functions that take them as arguments makes it possible to write really complex, powerful code in a concise yet highly expressive way. So we've looked at functions and closures, and even a bit of generic functions. I'd like to invite Alex on stage to tell you all about data types in Swift.
[ Applause ]
Thanks, Brian. You've seen how to use existing data types in Swift. Now let's take a look at how to create some data types of our own. Let's start with structures. You create one like this. You write the struct keyword followed by the name of a structure. Here, rectangle. Then inside the curly braces you write the properties of the structure.
Properties use the same syntax you've seen before for variables and constants. Both properties here have the value assigned as part of the declaration, so you can create a rectangle by just writing rectangle, the name, followed by a pair of parentheses. And to access the properties, you use dot notation.
You don't have to provide a value for a property as part of the types declaration. In fact, most of the time you don't. More often, you write just the type and then you set the value when you create the instance. So another property of a rectangle besides its dimensions is its area. You wouldn't want to store the area though. You'd have to update it every time the dimensions changed. What you want is a property whose value is calculated right at the point you need it and you can do that using a computed property.
To make a computed property, after the property's name and its type, you write a function body that's responsible for computing and returning the current value of the function. Even though it's computed rather than being stored, you still access it using dot notation. There's more that you can do with properties.
You can have setters and observers, and you can find out information about those in the Properties chapter of the Swift Programming Language. Just like you can define properties on a structure, you can also define methods. They use the same syntax you've seen already for functions. And just like properties, you access them using dot notation when you need to call them.
You've seen this syntax a few times to create a rectangle. Let's look at what it does in a little bit more detail. It looks a little bit like a function call because of the parenthesis, but it's actually calling an initializer. So far we've been using a special initializer called a member wise initializer that Swift implements for you on structures. Here's what it would look like if you write that initializer out explicitly.
You write the init keyword and then inside you set a value for the rectangle's properties. Notice that there are two things here called width. There's a property and there's a parameter, and you write self. to explicitly refer to the property. So that's how you write your own initializer.
There's enough code in this rectangle structure that it could benefit from some organization, and one way to do that in Swift is using extensions. An extension lets you add functionality to an existing type. In Swift you can use extensions to divide up your code. Core functionality can go in the structure's declaration and additional functionality can go in one or more extensions. You're not limited to extending your own types. If you need to, you can extend types from other places, such as foundation or the standard library.
You saw earlier how you can make a generic function which performs the same operations on data of different types. You can also make a generic structure which contains data of different types. The syntax looks very similar. You still have the generic type parameters inside angle brackets after the name. This example attaches a name to an array of some kind of element. This would be a useful data structure, for example, to populate a table view that has sections with heading names. You create a generic structure instance just like an instance of another type.
Here you can see if you write the type annotations, board games and primes have different types. One is an array of strings and the other is an array of integers. They have different element types so they are different types. You don't have to write the arrays. Swift infers what element is automatically.
So that's structures. Another data type in Swift is classes. You write class before the name, but everything about structures still applies. Properties, methods, initializers, and so on. So why would you want to create a class? One reason is that your code can refer to the same instance of a class from several different places, and that's different than the behavior you have for structures.
A structure acts like one big value. For example, in a game, if you make a score for player1, and then give player2 the same score, you have two independent scores. Changing player2's score doesn't change player1's score. However, both players need to refer to the same file on disk when they log their high scores. Since it's a class, that's the behavior that you get. When player2 gets a new high score and then logs it to the file, both players see the change.
If you're coming from another object oriented language, you might be used to writing a lot of classes, and in Swift you don't need to write them as often as you might think. Anytime it makes sense to check for equality or to make a copy, you usually want a value type like a structure. For more information about how and why to use value types, there's a great talk you can check out. Another reason to use classes is when you need to subclass an existing class.
For example, suppose you have a framework that gives you this fish class with sort of core fish functionality. You can subclass it to add functionality, like a FlyingFish can add a flying method. You indicate that this is a subclass by writing colon fish. Subclasses can also override a method to provide their own implementation. Like this ComplainingFish. It swims like a normal fish, it just complains about it first.
You write super.swim to call the superclass' implementation. This code doesn't compile yet. When you override a method in Swift, you have to mark it explicitly by writing override, and now this code works. Just like it's an error to override something by accident, it's also an error to write override when you don't override anything. That means if you misspell a method name that you're trying to override, Swift tells you about the error right at compile time.
A segue can also provide an initializer. For example, fish that has an initializer that takes a name. ComplainingFish needs an initializer with both a name and a complaint. Inside the initializer you set the initial value for the properties declared by the subclass, and then you call super.init to let the superclass finish the initialization. There's more you can do with initializers, especially around classes. You can read all about it in the Initializers chapter of the Swift Programming Language. You've seen how you can use subclasses. Now let's talk about a subclassing problem.
Continuing the game example, suppose you have this player base class. Since every player can take a turn, there's a method to do that. You have two kinds of players. You have a HumanPlayer class, which takes its turn by showing UI to the user. And you have a ComputerPlayer class which takes its turn by finding the best legal move.
So the question then is what goes in the base class implementation? Well, there's no shared behavior between human and computer players. There isn't any shared code to factor out. And in the surrounding code you would never want an instance of the player base class. So this method should never be called.
The only reasonable thing you could write here is some sort of fatal error to help you catch mistakes early in the development process. All the player class is doing is describing what it means to be a player, that every player can take a turn. It's a sort of blueprint for players.
In Swift you express this kind of relationship using a protocol. Protocols specify requirements like methods and properties. They're like interfaces or abstract classes that you might know from other languages. You make one using the protocol keyword. And because it describes requirements for other types to fulfill, you don't provide an implementation. Types conform to a protocol by providing that implementation and you declare conformance by writing : player, just like you do when there's a superclass. At this point you're not subclassing anymore so these methods are not overriding anything. So you don't write override.
There's no real reason these need to be classes anymore, so let's make them structures. And let's take a closer look at HumanPlayer. It has a few other properties, like a name and a score. And if you make an instance of player, you can print it out. You get a default description here because the standard library has a conversion from any type to a string.
But what if you wanted to customize the conversion to print a nicer description? The standard library also has a protocol called CustomStringConvertible which lets you provide a custom description, and here's what that protocol looks like. It has one requirement -- a description property. Any type that conforms to this protocol uses the custom description. Okay. So where should you implement the description property.
Remember from earlier how you can organize your code using extensions with core functionality in type declarations, and additional functionality in extensions. Having a custom string conversion definitely falls into the second category. It's not core functionality, so let's put it in an extension. Here's how you extend a type to add protocol conformance. You write colon CustomStringConvertible in the first line. And then you implement the requirements inside the extension body.
Now when you call print, you get the customized string conversion. There's a lot that you can do in Swift using protocols and extensions to organize your code and create abstractions. For more information check out this talk on Protocol Oriented Programming from 2015. That brings us to the last data type in Swift -- enumerations. You use an enumeration when there's a list of values that you know ahead of time. Here's an enumeration that supports left and right alignment for text.
When you use an enumeration, you use dot notation to access its cases. Here there are only two cases, so it's still readable if you write them on one line. Because an enumeration has a list of possible values, it's very common to use one with a switch, one switch case for each enumeration case.
Writing alignment over and over is a little repetitive and here it's not necessary. The switch is considering textAlignment, so the only possible values to check for are enumeration cases from that alignment. That means you can omit the enumeration name and write just .left. Also, notice that there's no default case here. That's for the same reason you saw earlier. The switch already covers every possible alignment value, so there's nothing left for a default case to handle.
Omitting the default case has a nice advantage. If you come back later and add a new enumeration case, but forget to update the switch, Swift will highlight the error until you add the missing code. Now let's take a quick look at two more things you can do with enumerations.
You can associate values with each enumeration case. For example, the alignment can specify how much padding to use and you can get that padding value back out as part of the switch case. You can also give each enumeration case a raw value from some other type, such as a string or an integer, which lets you improve type safety in your code by using enumerations instead of string constants. You've seen a lot of Swift this afternoon, but there isn't time to show you everything. One last thing I'd like to show you is error handling.
In Swift you can use an enumeration to describe errors. You use throws to mark a function that can throw an error. You write defer before a block of code that must be executed, whether or not there's an error, and that execution happens just before exiting scope, such as returning from a function.
Before calling code that might throw an error, you mark it with try, and you can catch and throw errors using catch and throw. For all the details about error handling, take a look at the Error Handling chapter in the Swift Programming Language. You can find a link to that book and other resources here. There are lots of other great Swift talks to check out later this week or on video. Thank you.
[ Applause ]