Closures in Swift
In this tutorial, we will cover the uses of closures in Swift.
A Closure is a type of special function, unique to Swift, that can be made without the function name. It is basically a function without the function name.
The main advantage of using closures is that instead of writing a function and then creating another instance calling the function, closures have the ability to run synchronously in the background. This is not only time-saving for advanced tasks, but it is also extremely effective at storage management since closures use significantly less memory compared to proper functions.
Declaring a Swift Closures
The main syntax difference between closures and functions is that we don’t have to use the func keyword, instead, we can simply create a closure as such:
{ (parameters) -> returnType in //code }
A closure must always be encased between braces: “{ }“, and each closure must contain parameters which is any value or variable passed by the closure, as well as the “in” keyword that is used to distinguish the body of closure with the listed parameters. However, a closure is mandated to have a returnType, rather it can be used to create a variable or void function. An example of this is:
var printNames = { print("Angela, David, Tony") } //running the closure printNames()
Here, as opposed to creating a function that returns the names of the people, then writing a print statement to print the names of the people we can instead create a closure that runs the code within its body, which in case of printing the list of name.
The major advantage of calling using a closure here would be using the printNames closure over a function. Instead of calling a function several times to access its values, we can use this variable to extract and modify it as required. This is especially handy when working with complex function types, like Nested Swift Functions.
Accessing a Closure using parameters
Similar to Function, Closures can also be used to parse parameters to be used by the closure:
// a closure uses 2 integer parameter let sum = { (num1: Int, num2: Int) in var numSum: Int = num1 + num2 print("The sum of \(num1) and \(num2) is \(numSum)") // calling the closure sum(2,5)
//Output The sum of 2 and 5 is 7
Just like we would do using a function, the immutable variable “sum” has been assigned a closure, which prints the sum of our parameters, 2 integers “num1” and “num2” and print it. And the syntax for calling a closure is exactly the same as if it were a function, but the advantage here is that that sum is a variable, and it can be used in another function. Let’s explore this utility in the following example.
Parsing a closure as Function Parameter
As mentioned previously, closures are preferable over functions as they can be stored in the background without the need to call them time after time, unlike functions. Because of this property, we can use the output from a closure within a function. For example:
// create a function that has a closure as its parameter func mostPopulated(findCountry: ()->()) { print("The most populated country is:") //call the closure findCountry() } // running the function and closure mostPopulated(findCountry: { print("China - 1.4 billion") } )
The most populated country is: China - 1.4 billion
As shown, the closure “findCountry” is passed in the function “mostPopulated” acting like a parameter. To pass a closure as a parameter, you need to use “() -> ()” to indicate that the parameter is of type closure. Now the closure is passed as the argument of the function, which prints the output.
Types of Special Closures
Trailing Closures
In Swift, if the last parameter of a function is a closure, we can implement a special type of syntax to parse it into a function, and such types of special closures are known as Trailing Closures. The property unique to trailing closures is that the actual code for the closure can be written outside the functions as so:
func getSpecialItem(greeting: String, menuItem: ()->()) { print(greeting) menuItem() } // Trailing closure: getSpecialItem(greeting:"Today's Special") { print("Butter Chicken") }
//Output: Today's Special Butter Chicken
As shown, the parent function getSpecialItem() encapsulates the trailing closure without the need to bind the closure within the body of the function. Since the closure is the last parameter of getSpecialItem(), we can use the syntax of a trailing closure, which allows us to create the closure and call the function simultaneously. This is particularly beneficial when changing the value of the closure, and since it is declared outside the function we can choose to parse different values by calling the function multiple times, instead of having to change the value of our function each time with separate instances.
Autoclosures
Continuing the example above, we can further simplify our code such that we do not need to use braces “{}” when passing our trailing closure. In order to pass our trailing closure without the use of braces, we instead need to use the keyword “@autoclosure” when declaring our parameters of the parent function like so:
print("Today's Special") //Parent Function with autoclosure declaration: func getSpecialItem(menuItem: @autoclosure ()->()) { menuItem() } // Calling the autoclosure without braces: getSpecialItem(menuItem: print("Chicken Curry"))
Autoclosures are mostly used to make the code more readable and sophisticated. A practical example would be using auto closures when designing the UI/UX:
- Using a function
UIView.animate(withDuration: 1.5) { self.view.backgroundColor = .green }
- Using an autoclosure:
UIView.animate(withDuration: 1.5, self.view.backgroundColor = .green)
This is how closures can be used within functions, which is extremely useful in event handling and callbacks in the background, since they are able to dispatch a network task in the background and also capture values from its surrounding context. This makes closures a very practical implementation in several real-life apps, for example, a fitness app that tracks steps even if the fitness application is not open, really it uses a series of closures to collect data even if the application itself is not open at all times.
Leave a Reply