Swift Closure
- Closures are self-contained blocks of functionality that can be passed around and used in your code.
- Closures can capture and store references to any constants and variables from the context in which they are defined.
- The concept of Swift Closure is similar to blocks in C. Closures are nameless functions and without the keyword func.
- Closures can take one of the three forms.
- Global functions
- Nested functions
- Closures expression
-
Global and nested functions were discussed at length in the Functions in Swift tutorial.
Advantages of Closures in Swift
- Closures are much less verbose when compared to Functions. Swift can infer the parameter types and return type from the context in which the closure is defined thereby making it convenient to define and pass to a function
- Swift Closure can be used to capture and store the state of some variable at a particular point in time and use it later(We’ll be discussing this at length later in the tutorial)
- Closures allow us to run a piece of code even after the function has returned
Swift Closure Syntax
Let’s recall the syntax of Functions in swift first.
func name(parameters) -> (return type) { //body of function }
Functions require a func keyword followed by name of function, arguments and return type after ->
The syntax for Closures is given below.
{ (parameters) -> (return type) in //body of the closure }
Closures are defined within curly braces. The in keyword separates the arguments and return type from the body. Like functions, closures can also be referenced as types.
Declaring Swift Closures as variables
var myClosure: (ParameterTypes) -> ReturnType
Let’s dive into the playground in XCode and start coding. Let’s create a basic function and a closure that’ll print “Hello World”.
import Foundation func helloWorldFunc() { print("Hello World") } var helloWorldClosure = { () -> () in print("Hello World") } helloWorldFunc() //prints Hello World helloWorldClosure() //prints Hello World
In the above code, we’ve defined a closure with no parameters and no return type and set it to a variable helloWorldClosure. Calling the closure is similar to calling a function as shown above.
Tip 1: If the closure does not return any value or the type can be inferred you can omit the arrow (->) and the return type. We can shorten the above code as shown below.
var helloWorldClosure = { () in print("Hello World") }
Tip 2: If the closure doesn’t require any parameter or can be inferred, remove the argument parenthesis. Now we don’t need the in keyword too. This will further shorten the code as shown below.
var helloWorldClosure = { print("Hello World") }
We’ve seen one use case now where closures made the code look less verbose compared to a function. Let’s create a closure with parameters and see how it fares when compared to closures.
Swift Closures with arguments and return type
Let’s create a function and closure that accepts two strings, appends them and return.
import Foundation func appendString(_ a: String, with b: String) -> String { return a + " : " + b } print(appendString("Swift", with: "Functions")) //print "Swift : Functions\n"
So far so good. Let’s do the same using a closure.
import Foundation var appendStringClosure = { (a: String, b: String) -> (String) in return a + " : " + b } print(appendStringClosure("Swift", "Closures")) //prints "Swift : Closures\n"
Thanks to type inference as we’d seen earlier, the above closure definition is the same as the following.
import Foundation var appendStringClosure = { (a: String, b: String) in return a + " : " + b // return type inferred }
another version can be like this
import Foundation var appendStringClosure: (String, String) -> String = { (a,b) in return a + " : " + b //Closure type declared on the variable itself }
We can create a closure without return keyword also
import Foundation var appendStringClosure: (String, String) -> String = { (a,b) in a + " : " + b // omit return keyword }
There’s even a shortened version for the above. Swift allows us to refer to arguments passed to closure using shorthand argument names: $0, $1, $2, etc. for the first, second, third, etc. parameters respectively.
import Foundation var appendStringClosure: (String, String) -> String = { $0 + " : " + $1 } print(appendStringClosure("Swift", "Closures")) //prints "Swift : Closures"
Let’s take it one more step ahead, add as many arguments as you can.
import Foundation var appendStringClosure = { $0 + " : " + $1 + " " + $2 } print(appendStringClosure("Swift", "Closures", "Awesome")) //prints "Swift : Closures Awesome"
Closures inside Functions
Let’s create a function that takes a function/closure as a parameter.
import Foundation func operationsSq(a: Int, b:Int, myFunction: (Int, Int) -> Int) -> Int { return myFunction(a,b) } // This function expects a parameter of type (Int, Int)->Int as the third argument. // Lets pass a function in there as shown below func addSquareFunc(_ a: Int, _ b:Int) -> Int { return a*a + b*b } print(operationsSq(a:2,b:2, myFunction: addSquareFunc)) //a^2 + b^2 prints 8
Fair enough. But creating a different function for another operation (let’s say subtracting squares etc) would keep increasing the boilerplate code. This is where closure comes to our rescue. We’ve defined the following 4 types of expressions that we’ll be passing one by one in the operationsSq function.
import Foundation func operationsSq(a: Int, b:Int, myFunction: (Int, Int) -> Int) -> Int { return myFunction(a,b) } var addSq: (Int, Int) -> Int = { $0*$0 + $1*$1 } var subtractSq: (Int, Int) -> Int = { $0*$0 - $1*$1 } var multiplySq: (Int, Int) -> Int = { $0*$0 * $1*$1 } var divideSq: (Int, Int) -> Int = { ($0*$0) / ($1*$1) } var remainderSq: (Int, Int) -> Int = { ($0*$0) % ($1*$1) } print(operationsSq(a:2, b:2, myFunction: addSq)) //prints 8 print(operationsSq(a:4, b:5, myFunction: subtractSq)) //prints -9 print(operationsSq(a:5, b:5, myFunction: multiplySq)) //prints 625 print(operationsSq(a:10, b:5, myFunction: divideSq)) //prints 4 print(operationsSq(a:7, b:5, myFunction: remainderSq)) //prints 24
Much shorter than what we achieved with a function as a parameter. In fact, we can directly pass the closure expressions as shown below and it’ll achieve the same result:
import Foundation func operationsSq(a: Int, b:Int, myFunction: (Int, Int) -> Int) -> Int { return myFunction(a,b) } operationsSq(a:2,b:2, myFunction: { $0 * $0 + $1 * $1 }) operationsSq(a:4,b:5, myFunction: { $0 * $0 - $1 * $1 }) operationsSq(a:5,b:5, myFunction: { $0 * $0 * $1 * $1 }) operationsSq(a:10,b:5, myFunction: { ($0 * $0) / ($1 * $1) }) operationsSq(a:7,b:5, myFunction: { ($0 * $0) % ($1 * $1) })
Trailing Closures
- When the closure is being passed as the final argument to a function we can pass it as a trailing closure.
- A trailing closure is written after the function call’s parentheses, even though it is still an argument to the function.
- When you use the trailing closure syntax, you don’t write the argument label for the closure as part of the function call.
- If closure argument is the sole argument of the function you can remove the function parentheses. Hence the previous closures can be written as the following and it would still function the same way.
Let’s look at another example where we’ll be adding the sum of exponentials of numbers using closure expressions.
import Foundation func sumOfExponentials(from: Int, to: Int, myClosure: (Int) -> Int) -> Int { var sum = 0 for i in from...to { sum = sum + myClosure(i) } return sum } //Trailing closures print(sumOfExponentials(from:0, to:5) { $0 }) //sum of numbers prints 15 print(sumOfExponentials(from:0, to:5) { $0*$0 }) //sum of squares prints 55 print(sumOfExponentials(from:0, to:5) { $0*$0*$0 }) //sum of cubes prints 225
Convert a numbers array to strings array
Another use case of trailing closure is given below.
import Foundation var numbers = [1,2,3,4,5,6] print(numbers) var strings = numbers.map { "\($0)" } print(strings) //prints ["1", "2", "3", "4", "5", "6"]
map is a high order function for transforming an array.
Sort numbers in descending order using trailing closures
import Foundation var randomNumbers = [5,4,10,45,76,11,0,9] randomNumbers = randomNumbers.sorted{ $0 > $1 } print(randomNumbers) //[76, 45, 11, 10, 9, 5, 4, 0]
Capture Lists in Closures
By default, closures can capture and store references to any constants and variables from the context in which they are defined (Hence the name closures). Capturing references to variables can cause our closures to behave differently than it’s supposed to. Let’s see this through an example.
import Foundation var x = 0 var myClosure = { print("The value of x at start is \(x)") } myClosure() //prints 0
So far so good. The above example looks pretty straightforward until we do this:
import Foundation var x = 0 var myClosure = { print("The value of x at start is \(x)") } myClosure() //The value of x at start is 0 x = 5 myClosure() //The value of x at start is 5
Now myClosure was defined and initialized before changing the value of x to 5. Why does it print 5 in the end then?
The reason is that closure captures the reference (memory address) of x. Any changes made to value at that memory address would be displayed by the closure when it’s invoked. To make x behave like a value type instead we need to make use of Capture Lists. Capture Lists is an array [] that holds local copies of the variables. Thereby capturing the variables by value types and NOT reference types.
The array with the local variables is displayed before the in keyword as shown below.
import Foundation var x = 0 var myClosure = { [x] in print("The value of x at start is \(x)") } myClosure() // The value of x at start is 0 x = 5 myClosure() //The value of x at start is 0
Capturing reference types can be destructive when used in Classes since the closure can hold a strong reference to an instance variable and cause memory leaks. Let’s see an example of this.
import Foundation class Person { var x: Int var myClosure: () -> () = { print("Hey there") } init(x: Int) { self.x = x } func initClosure() { myClosure = { print("Initial value is not defined yet") } } deinit { print("\(self) Escaped") } } var a: Person? = Person(x: 0) a?.initClosure() a?.x = 5 a?.myClosure() a = nil
Deinitializers are called automatically, just before instance deallocation takes place. In the above code, it’ll be called when a is set to nil and self doesn’t have any references attached to it. The above code would print:
[quote font=”verdana”] Initial value is not defined yet __lldb_expr_55.Person Escaped [/quote]
import Foundation class Person { var x: Int var myClosure: () -> () = { print("Hey there") } init(x: Int) { self.x = x } func initClosure() { myClosure = { print("Intial value is \(self.x)") } } deinit { print("\(self) escaped") } } var a: Person? = Person(x: 0) a?.initClosure() a?.x = 5 a?.myClosure() a = nil
Whoops! the print statement within deinit is not printed. The closure holds a strong reference to self. This will eventually lead to memory leaks. A remedy for this is to place a weak or unknown reference to self in a capture list inside the closure as seen below.
- A weak reference is a reference that does not keep a strong hold on the instance it refers to and so does not stop ARC from disposing of the referenced instance.
- An unowned reference does not keep a strong hold on the instance it refers to. Unlike a weak reference, however, an unowned reference is used when the other instance has the same lifetime or a longer lifetime.
The code for capture list with self-reference is given below:
import Foundation class Person { var x: Int var myClosure: () -> () = { print("Hey there") } init(x: Int) { self.x = x } func initClosure() { myClosure = { [weak self] in guard let weakSelf = self else { return } print("Intial value is \(weakSelf.x)")} } deinit { print("\(self) escaped") } } var a: Person? = Person(x: 0) a?.initClosure() a?.x = 5 a?.myClosure() a = nil
No memory leaks with the above code!
Escaping Closures
There are two other types of closures too:
- An escaping closure is a closure that’s called after the function it was passed to returns. In other words, it outlives the function it was passed to. An escaping closure is generally used in completion handlers since they are called after the function is over.
- A non-escaping closure is a closure that’s called within the function it was passed into, i.e. before it returns. Closures are non-escaping by default
import Foundation var completionHandlers: [() -> Void] = [] func someFunctionWithNonescapingClosure(closure: () -> Void) { closure() } func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) { completionHandlers.append(completionHandler) } class SomeClass { var x = 10 func doSomething() { someFunctionWithEscapingClosure { [weak self] in guard let weakSelf = self else { return } weakSelf.x = 100 } someFunctionWithNonescapingClosure { x = 200 } } deinit { print("deinitalised") } } var s:SomeClass? = SomeClass() s?.doSomething() print(s?.x ?? -1) //prints 200 completionHandlers.first?() print(s?.x ?? -1) //prints 100 s = nil
completionHandlers.first() returns and invokes the first element of the array which is an escaping closure. Since we’d set a value type inside it using capture lists, it prints 100. We’ll be discussing escaping closures at length in a later tutorial.
One Response