Closures are also referred to as anonymous functions, and this is the best way to explain them. Closures are functions that optionally take a set of input parameters, and they optionally return an output, the same as other functions. However, rather than having a function name, closures behave like other primary types--they can be assigned, stored, passed around, and used as the input and output to functions and other closures.
Closures
Getting ready
We will continue to build on our contacts app example from earlier in this chapter, so you should use the same playground as in the previous recipes. Don't worry if you haven't followed the previous recipes; all the code needed is listed here, so you can enter this in a new playground.
How to do it...
If you are implementing this in a new playground, enter the following:
struct PersonName {
let givenName: String
let middleName: String
var familyName: String
func fullName() -> String {
return "\(givenName) \(middleName) \(familyName)"
}
mutating func change(familyName: String) {
self.familyName = familyName
}
}
class Person {
let birthName: PersonName
var currentName: PersonName
var countryOfResidence: String
init(name: PersonName, countryOfResidence: String = "UK") {
birthName = name
currentName = name
self.countryOfResidence = countryOfResidence
}
var displayString: String {
return "\(currentName.fullName()) - Location: \(countryOfResidence)"
}
}
You will need to go back over the previous recipes to see how these were created.
Now, let's put a number of closure types in the playground so we examine them:
// No input, no output
let printAuthorsDetails: () -> Void = {
let name = PersonName(givenName: "Keith", middleName: "David", familyName: "Moon")
let author = Person(name: name)
print(author.displayString)
}
printAuthorsDetails() // "Keith David Moon - Location: UK"
// No input, Person output
let createAuthor: () -> Person = {
let name = PersonName(givenName: "Keith", middleName: "David", familyName: "Moon")
let author = Person(name: name)
return author
}
let author = createAuthor()
print(author.displayString) // "Keith David Moon - Location: UK"
// String inputs, no output
let printPersonsDetails: (String, String, String) -> Void = { givenName, middleName, familyName in
let name = PersonName(givenName: givenName, middleName: middleName, familyName: familyName)
let author = Person(name: name)
print(author.displayString)
}
printPersonsDetails("Kathyleen", "Mary", "Moon") // "Kathleen Mary Moon - Location: UK"
// String inputs, Person output
let createPerson: (String, String, String) -> Person = { givenName, middleName, familyName in
let name = PersonName(givenName: givenName, middleName: middleName, familyName: familyName)
let person = Person(name: name)
return person
}
let melody = createPerson("Melody", "Margaret", "Moon")
print(melody.displayString) // "Melody Margaret Moon - Location: UK"
How it works...
Let's take a look at the different types of closures we just implemented:
// No input, no output
let printAuthorsDetails: () -> Void = {
let name = PersonName(givenName: "Keith", middleName: "David", familyName: "Moon")
let author = Person(name: name)
print(author.displayString)
}
As a first-class type in Swift, closures can be assigned to constants or variables, and constants and variables need a type. To define a closure's type, we need to specify the input parameter types and the output type, and for the closure in the preceding code, the type is () -> Void. The Void type is another way of saying nothing, so this closure takes no inputs and returns nothing, and the closure's functionality is defined within the curly brackets, as with other functions.
Now that we have this closure defined and assigned to the printAuthorsDetails constant, we can execute it as follows, which will cause this author's details to be printed:
printAuthorsDetails() // "Keith David Moon - Location: UK"
The next closure type takes no input parameters, but returns a Person object, as you can see with the () -> Person type definition:
// No input, Person output
let createAuthor: () -> Person = {
let name = PersonName(givenName: "Keith", middleName: "David", familyName: "Moon")
let author = Person(name: name)
return author
}
let author: Person = createAuthor()
print(author.displayString) // "Keith David Moon - Location: UK"
Since it has an output, the execution of the closure returns a value and can be assigned to a variable or constant. In the preceding code, we execute the createAuthor closure and assign the output to the author constant. Since we defined the closure type as () -> Person, the compiler knows that the output type is a Person and therefore the type of the constant can be inferred, so we don't need to explicitly declare it. Let's remove the explicit type declaration:
let author = createAuthor()
print(author.displayString) // "Keith David Moon - Location: UK"
Next, let's take a look at a closure that takes input parameters:
// String inputs, no output
let printPersonsDetails: (String, String, String) -> Void = { given, middle, family in
let name = PersonName(givenName: given, middleName: middle, familyName: family)
let author = Person(name: name)
print(author.displayString)
}
printPersonsDetails("Kathleen", "Mary", "Moon") // "Kathleen Mary Moon - Location: UK"
You will remember, from the recipe on functions, that we can define parameter labels, which define how the parameters are referenced when the function is used, and parameter names, which define how the parameter is referenced from within the function. In closures, these are defined a bit differently:
- Parameter labels cannot be defined for closures, so, when calling a closure, the order and parameter type have to be used to determine what values should be provided as parameters:
(String, String, String) -> Void
- Parameter names are defined inside the curly brackets, followed by the in keyword:
given, middle, family in
Putting it all together, we can define and execute a closure with inputs and an output, as follows:
// String inputs, Person output
let createPerson: (String, String, String) -> Person = { given, middle, family in
let name = PersonName(givenName: given, middleName: middle, familyName: family)
let person = Person(name: name)
return person
}
let melody = createPerson("Melody", "Margaret", "Moon")
print(melody.displayString) // "Melody Margaret Moon - Location: UK"
There's more...
We've seen how we can store closures, but we can also use them as method parameters. This pattern can be really useful when we want to be notified when a long-running task is completed.
Let's imagine that we want to save the details of our person object to a remote database, maybe for backup or use on other devices. We'll amend our Person class to include this save functionality, and in the process, see how we can pass in a closure, store it, and execute it at a later time.
Add the following code to our Person class:
class Person {
//....
var saveHandler: ((Bool) -> Void)?
func saveToRemoteDatabase(handler: @escaping (Bool) -> Void) {
saveHandler = handler
// Send person information to remove database
// Once remote save is complete, it calls saveComplete(Bool)
}
func saveComplete(success: Bool) {
saveHandler?(success)
}
}
We define an optional variable to hold onto the save handler during the long-running save operation. Our closure will take a Bool to indicate whether the save was a success:
var saveHandler: ((Bool) -> Void)?
Let's define a method to save our Person object, which takes a closure as a parameter:
func saveToRemoteDatabase(handler: @escaping (Bool) -> Void) {
saveHandler = handler
// Send person information to remove database
// Once remote save is complete, it calls saveComplete(Bool)
}
Our function stores the given closure in the variable, and then starts the process to save to the remote database (the actual implementation of this is outside the scope of this recipe). This save process will call the saveComplete method when completed. We added a modifier, @escaping, just before the closure type definition. This tells the compiler that, rather than using the closure within this method, we intend to store the closure and use it later. The closure will be escaping the scope of this method. This modifier is needed to prevent the compiler from doing certain optimizations that would be possible if the closure was nonescaping.
With the save operation complete, we can execute the saveHandler variable, passing in the success boolean. However, since we stored the closure as optional, we need to unwrap it by adding a ? after the variable name. If saveHandler has a value, the closure will be executed; if it is nil, the expression is ignored:
func saveComplete(success: Bool) {
saveHandler?(success)
}
Now that we have a function that takes a closure, let's see how we call it:
let dave = createPerson("David", "Ernest", "Moon")
dave.saveToRemoteDatabase(handler: { success in
print("Saved finished. Successful: \(success))")
})
Swift provides a more concise way to provide closures to functions. When a closure is the last (or only) parameter, Swift allows it to be provided as a trailing closure--that is, the parameter name can be dropped and the closure can be specified after the parameter brackets. So we can, instead, do the following:
dave.saveToRemoteDatabase { success in
print("Saved finished. Successful: \(success))")
}
See also
Further information about closures can be found in Apple's documentation of the Swift language at http://swiftbook.link/docs/closures.