Agenda
- Create and use closures
- Trailing closures and shorthand syntax
- Accept functions as parameters
- Summary: Closures
- Checkpoint 5
Create and use closures
Create a copy of a function
When you create a function, you can create a copy of it and assign it to a variable and then call the copy. Example.
func greetUser() {
print("Hi there!")
}
greetUser()
//Creating a copy of the function greetUser
var greetCopy = greetUser
greetCopy()
In the above example, the function greetUser()
is copied into a variable greetCopy
. Note that when you are copying a function, you should not include parentheses. If you do so, you will be calling the function and assigning the return value of that function to the variable.
Now that you have made a copy of the function and called the copy, you will be printing Hi there!
twice, one from the original function and the other from the copy.
Closures
You can skip creating a separate function and add functionality directly to a variable or constant.
var greeting = {
print("Hi there")
}
greeting()
This is called a closure expression. A closure is essentially a function that doesn’t take any parameters and doesn’t return a value.
Closures with Parameters
Typically in a function, you will add parameters and return data type outside the braces. But when you are writing a closure, everything should be inside the braces including the parameters and return type of the closure.
let sayHello = { (name: String) -> String in
"Hi \(name)!"
}
See the keyword in
at the end of the first line that indicates the end of parameters and return type. Everything after that is the body of the closure.
Function types
Like integers and doubles, functions also have types. Let us look at an example to better understand the concept.
var greetCopy: () -> Void = greetUser
The above line of code is type annotation for greetCopy which accepts no parameters and doesn’t return any values.
The breakdown of the code is as follows:
- The empty parentheses indicate that the function takes no parameters.
- The arrow indicates that we are declaring the return type of the function
- Void indicates that the function returns nothing. We can also use () to indicate the term Void.
It is important to note that only the type of parameters and return value are a part of function type annotation but not the name of the parameters. Take a look at this example.
func getUserData(for id: Int) -> String {
if id == 1989 {
return "Taylor Swift"
} else {
return "Anonymous"
}
}
let data: (Int) -> String = getUserData
let user = data(1989)
print(user)
See, we are using data(1989)
to pass the parameter to the function instead of data(for: 1989)
. The same rule applies to closures as well.
let sayHello = { (name: String) -> String in
"Hi \(name)!"
}
sayHello("Taylor")
To reiterate, external parameter names only matter when we are calling a function directly, not when we create a closure or when we take a copy of the function first.
Passing functions into another function
Functions can be passed as parameters to another functions. To understand the concept, let us look at the example of sorted()
method in arrays.
sorted()
accepts parameters as well. One such parameter is sorted(by:)
. This gives us control on how we want to sort the array.
let team = ["Gloria", "Suzanne", "Piper", "Tiffany", "Tasha"]
let sortedTeam = team.sorted()
print(sortedTeam)
The above piece of code is general use of sorted()
method. Now, if you want to place a team member name in the front, swift allows you to do that by passing a custom function as parameter.
func captainFirstSorted(name1: String, name2: String) -> Bool {
if name1 == "Suzanne" {
return true
} else if name2 == "Suzanne" {
return false
}
return name1 < name2
}
The function says how to order two items of the array if one of the two items is “Suzanne”. If “Suzanne” comes first, it returns true means it will be sorted first and the other item is sorted next. If neither of the items is “Suzanne” then they are sorted alphabetically.
Now that we have written our custom function, we have to pass it as a parameter to sorted()
.
let captainFirstTeam = team.sorted(by: captainFirstSorted)
print(captainFirstTeam)
Note: sorted()
can be passed a function as long as that function accepts two strings as input and returns a Boolean.
Closure as a parameter to a function
We have seen two concepts till now, closures and passing functions to another function. Swift lets us combine them both. sorted()
just wants a function that will accept two strings and will return a Boolean. It doesn’t care if that function is created by using func
or by closures.
let captainFirstTeam = team.sorted(by: { (name1: String, name2: String) -> Bool in
if name1 == "Suzanne" {
return true
} else if name2 == "Suzanne" {
return false
}
return name1 < name2
})
print(captainFirstTeam)
The above code shows how we used closures to pass a custom function as a parameter to sorted()
.
Trailing closures and shorthand syntax
Take another look at the previous piece of code. See how long the first line is where we mention the name of the parameters, their type and return type of the closure.
In this particular case, we know the purpose of our closure and also what it does. It accepts two strings as parameters and returns a Boolean. If you provide any other parameter types or did not return a Boolean in sorted(by:)
, your code won’t build. As we know the parameter and return types, we can avoid mentioning them in our code. The modified code looks like this.
let captainFirstTeam = team.sorted(by: { name1, name2 in
Trailing Closure
We can eliminate the use of (by:
and the closing parentheses at the end. This is called as trailing closure syntax.
let captainFirstTeam = team.sorted { name1, name2 in
if name1 == "Suzanne" {
return true
} else if name2 == "Suzanne" {
return false
}
return name1 < name2
}
Shorthand syntax
By using this syntax, we don’t have to mention parameter names. Instead, swift will provide named values for us: $0
and $1
for the first and second string respectively.
let captainFirstTeam = team.sorted {
if $0 == "Suzanne" {
return true
} else if $1 == "Suzanne" {
return false
}
return $0 < $1
}
Let us take another example that will demonstrate the use of shorthand syntax. Say we are just reversing the order of the array by using a closure, we can do it like this.
let reverseTeam = team.sorted {
return $0 > $1
}
Remember, previously we have discussed about scenarios where we can eliminate the use of keyword return
even when we are returning a value. This can be done when your function has only one line of code. It can be applied to closures as well.
let reverseTeam = team.sorted { $0 > $1 }
Examples for closures
The use of filter()
in arrays. If you want to filter an array based on the prefix of each item, you can do so by the use of closure.
let team = ["Gloria", "Suzanne", "Piper", "Tiffany", "Tasha"]
let tOnly = team.filter { $0.hasPrefix("T") }
print(tOnly)
This will print ["Tiffany","Tasha"]
.
Transforming every item of an array into uppercased by the use of map()
. The map()
will let us apply transform to every item of the array and returns a new array.
let uppercaseTeam = team.map { $0.uppercased() }
print(uppercaseTeam)
This will print [“GLORIA”,SUZANNE”,”PIPER”,”TIFFANY”,”TASHA”].
Accept functions as parameters
func makeArray(size: Int, using generator: () -> Int) -> [Int] {
var numbers = [Int]()
for _ in 0..<size {
let newNumber = generator()
numbers.append(newNumber)
}
return numbers
}
Let’s break that down…
- The function is called
makeArray()
. It takes two parameters, one of which is the number of integers we want, and also returns an array of integers. - The second parameter is a function. This accepts no parameters itself, but will return one integer every time it’s called.
- Inside
makeArray()
we create a new empty array of integers, then loop as many times as requested. - Each time the loop goes around we call the
generator
function that was passed in as a parameter. This will return one new integer, so we put that into thenumbers
array. - Finally the finished array is returned.
Using closure to pass generator function as parameter
The previous code clearly says what is size parameter. But we don’t know the functionality of generator()
. That is where closure comes into play.
let rolls = makeArray(size: 50) {
Int.random(in: 1...20)
}
print(rolls)
The piece of code inside {}
acts as the generator function. In this case, we are getting a random number from 1 to 20 and returning that value. Once that value is returned, it is appended to the array numbers
.
The same functionality can be applied by the use of functions.
func generateNumber() -> Int {
Int.random(in: 1...20)
}
let newRolls = makeArray(size: 50, using: generateNumber)
print(newRolls)
In this case, generateNumber
acts as the generator()
.
Multiple functions as parameters
To better understand this, let us look at the example below.
func doImportantWork(first: () -> Void, second: () -> Void, third: () -> Void) {
print("About to start first work")
first()
print("About to start second work")
second()
print("About to start third work")
third()
print("Done!")
}
doImportantWork {
print("This is the first work")
} second: {
print("This is the second work")
} third: {
print("This is the third work")
}
The result can be seen in the screenshot below.

Summary: Closures
- You can copy functions in Swift, and they work the same as the original except they lose their external parameter names.
- All functions have types, just like other data types. This includes the parameters they receive along with their return type, which might be
Void
– also known as “nothing”. - You can create closures directly by assigning to a constant or variable.
- Closures that accept parameters or return a value must declare this inside their braces, followed by the keyword
in
. - Functions are able to accept other functions as parameters. They must declare up front exactly what data those functions must use, and Swift will ensure the rules are followed.
- In this situation, instead of passing a dedicated function you can also pass a closure — you can make one directly. Swift allows both approaches to work.
- When passing a closure as a function parameter, you don’t need to explicitly write out the types inside your closure if Swift can figure it out automatically. The same is true for the return value — if Swift can figure it out, you don’t need to specify it.
- If one or more of a function’s final parameters are functions, you can use trailing closure syntax.
- You can also use shorthand parameter names such as
$0
and$1
, but I would recommend doing that only under some conditions. - You can make your own functions that accept functions as parameters, although in practice it’s much more important to know how to use them than how to create them.
Checkpoint 5
Challenge
let luckyNumbers = [7, 4, 38, 21, 16, 15, 12, 33, 31, 49]
Your job is to:
- Filter out any numbers that are even
- Sort the array in ascending order
- Map them to strings in the format “7 is a lucky number”
- Print the resulting array, one item per line
So, your output should be as follows:
7 is a lucky number
15 is a lucky number
21 is a lucky number
31 is a lucky number
33 is a lucky number
49 is a lucky number
Solution
let luckyNumbers = [7,4,38,21,16,15,12,33,31,49]print(luckyNumbers.filter{!$0.isMultiple(of: 2)}.sorted().map{print("\($0) is a lucky number")})
That is everything for today’s learning. See you tomorrow.