In this article, Jon Hoffman, the author of the book Mastering Swift 3 - Linux, talks about most major programming languages have functionalities similar to what closures offer. Some of these implementations are really hard to use (Objective-C blocks), while others are easy (Java lambdas and C# delegates). I found that the functionality that closures provide is especially useful when developing frameworks. I have also used them extensively when communicating with remote services over a network connection. While blocks in Objective-C are incredibly useful (and I have used them quite a bit), their syntax used to declare a block was absolutely horrible. Luckily, when Apple was developing the Swift language, they made the syntax of closures much easier to use and understand.
In this article, we will cover the following topics:
An introduction to closures
Defining a closure
Using a closure
Several useful examples of closures
How to avoid strong reference cycles with closures
(For more resources related to this topic, see here.)
An introduction to closures
Closures are self-contained blocks of code that can be passed around and used throughout our application. We can think of an Int type as a type that stores an integer, and a String type as a type that stores a string. In this context, a closure can be thought of as a type that contains a block of code. What this means is that we can assign closures to a variable, pass them as arguments to functions, and also return them from functions.
Closures have the ability to capture and store references to any variable or constant from the context in which they were defined. This is known as closing over the variables or constants, and the best thing is, for the most part, Swift will handle the memory management for us. The only exception is when we create a strong reference cycle, and we will look at how to resolve this in the Creating strong reference cycles with closures section of this article.
Closures in Swift are similar to blocks in Objective-C; however, closures in Swift are a lot easier to use and understand. Let's look at the syntax used to define a closure in Swift:
{
(parameters) -> return-type in
statements
}
As we can see, the syntax used to create a closure looks very similar to the syntax we use to create functions in Swift, and actually, in Swift, global and nested functions are closures. The biggest difference in the format between closures and functions is the in keyword. The in keyword is used in place of curly brackets to separate the definition of the closure's parameter and return types from the body of the closure.
There are many uses for closures, and we will go over a number of them later in this article, but first we need to understand the basics of closures. Let's start by looking at some very basic uses for closures so that we can get a better understanding of what they are, how to define them, and how to use them.
Simple closures
We will begin by creating a very simple closure that does not accept any arguments and does not return a value. All it does is print Hello World to the console. Let's take a look at the following code:
let clos1 = {
() -> Void in
print("Hello World")
}
In this example, we create a closure and assign it to the constant clos1. Since there are no parameters defined between the parentheses, this closure will not accept any parameters. Also, the return type is defined as Void; therefore, this closure will not return any value. The body of the closure contains one line that prints Hello World to the console.
There are many ways to use closures; in this example, all we want to do is execute it. We execute this closure such as this:
clos1()
When we execute the closure, we will see that Hello World is printed to the console. At this point, closures may not seem that useful, but as we get further along in this article, we will see how useful and powerful they can be.
Let's look at another simple closure example. This closure will accept one string parameter named name, but will still not return a value. Within the body of the closure, we will print out a greeting to the name passed into the closure through the name parameter. Here is the code for this second closure:
let clos2 = {
(name: String) -> Void in
print("Hello (name)")
}
The big difference between clos2 defined in this example and the previous clos1 closure is that we define a single string parameter between the parentheses. As we can see, we define parameters for closures just like we define parameters for functions.
We can execute this closure in the same way in which we executed clos1. The following code shows how this is done:
clos2(name: "Jon")
This example, when executed, will print the message Hello Jon to the console. Let's look at another way we can use the clos2 closure.
Our original definition of closures stated, closures are self-contained blocks of code that can be passed around and used throughout our application code. What this tells us is that we can pass our closure from the context that it was created in to other parts of our code. Let's look at how to pass our clos2 closure into a function. We will define a function that accepts our clos2 closure such as this:
func testClosure(handler:(String)->Void) {
handler("Dasher")
}
We define the function just like we would any other function; however, in our parameter list, we define a parameter named handler, and the type defined for the handler parameter is (String)->Void. If we look closely, we can see that the (String)->Void definition of the handler parameter matches the parameter and return types that we defined for clos2 closure. This means that we can pass the clos2 closure into the function. Let's look at how to do this:
testClosure(handler: clos2)
We call the testClosure() function just like any other function and the closure that is being passed in looks like any other variable. Since the clos2 closure executed in the testClosure() function, we will see the message, Hello Dasher, printed to the console when this code is executed.
As we will see a little later in this article, the ability to pass closures to functions is what makes closures so exciting and powerful.
As the final piece to the closure puzzle, let's look at how to return a value from a closure. The following example shows this:
let clos3 = {
(name: String) -> String in
return "Hello (name)"
}
The definition of the clos3 closure looks very similar to how we defined the clos2 closure. The difference is that we changed the Void return type to a String type. Then, in the body of the closure, instead of printing the message to the console, we used the return statement to return the message. We can now execute the clos3 closure just like the previous two closures, or pass the closure to a function like we did with the clos2 closure. The following example shows how to execute clos3 closure:
var message = clos3("Buddy")
After this line of code is executed, the message variable will contain the Hello Buddy string.
The previous three examples of closures demonstrate the format and how to define a typical closure. Those who are familiar with Objective-C can see that the format of closures in Swift is a lot cleaner and easier to use. The syntax for creating closures that we have shown so far in this article is pretty short; however, we can shorten it even more. In this next section, we will look at how to do this.
Shorthand syntax for closures
In this section, we will look at a couple of ways to shorten the definition of closures.
Using the shorthand syntax for closures is really a matter of personal preference. There are a lot of developers that like to make their code as small and compact as possible and they take great pride in doing so. However, at times, this can make code hard to read and understand by other developers.
The first shorthand syntax for closures that we are going to look at is one of the most popular and is the syntax we saw when we were using algorithms with arrays. This format is mainly used when we want to send a really small (usually one line) closure to a function, like we did with the algorithms for arrays. Before we look at this shorthand syntax, we need to write a function that will accept a closure as a parameter:
func testFunction(num: Int, handler:()->Void) {
for _ in 0..< num {
handler()
}
}
This function accepts two parameters—the first parameter is an integer named num, and the second parameter is a closure named handler that does not have any parameters and does not return any value. Within the function, we create a for loop that will use the num integer to define how many times it loops. Within the for loop, we call the handler closure that was passed into the function.
Now lets create a closure and pass it to the testFunction()such as this:
let clos = {
() -> Void in
print("Hello from standard syntax")
}
testFunction(num: 5,handler: clos)
This code is very easy to read and understand; however, it does take five lines of code. Now, let's look at how to shorten this code by writing the closure inline within the function call:
testFunction(num: 5,handler: {print("Hello from Shorthand closure")})
In this example, we created the closure inline within the function call using the same syntax that we used with the algorithms for arrays. The closure is placed in between two curly brackets ({}), which means the code to create our closure is {print("Hello from Shorthand closure")}. When this code is executed, it will print out the message, Hello from Shorthand closure, five times on the screen.
Let's look at how to use parameters with this shorthand syntax. We will begin by creating a new function that will accept a closure with a single parameter. We will name this function testFunction2. The following example shows what the new testFunction2 function does:
func testFunction2(num: Int, handler:(name: String)->Void) {
for _ in 0..< num {
handler(name: "Me")
}
}
In testFunction2, we define our closure such as this: (name: String)->Void. This definition means that the closure accepts one parameter and does not return any value. Now, let's see how to use the same shorthand syntax to call this function:
testFunction2(num: 5,handler: {print("Hello from ($0)")})
The difference between this closure definition and the previous one is $0. The $0 parameter is shorthand for the first parameter passed into the function. If we execute this code, it prints out the message, Hello from Me, five times.
Using the dollar sign ($) followed by a number with inline closures allows us to define the closure without having to create a parameter list in the definition. The number after the dollar sign defines the position of the parameter in the parameter list. Let's examine this format a bit more because we are not limited to only using the dollar sign ($) and number shorthand format with inline closures. This shorthand syntax can also be used to shorten the closure definition by allowing us to leave the parameter names off. The following example demonstrates this:
let clos5: (String, String) ->Void = {
print("($0) ($1)")
}
In this example, our closure has two string parameters defined; however, we do not give them names. The parameters are defined such as this: (String, String). We can then access the parameters within the body of the closure using $0 and $1. Also, note that closure definition is after the colon (:), using the same syntax that we use to define a variable type, rather than inside the curly brackets. When we use anonymous arguments, this is how we would define the closure. It will not be valid to define the closure such as this:
let clos5b = {
(String, String) -> Void in
print("($0) ($1)")
}
In this example, we will receive the Anonymous closure arguments cannot be used inside a closure that has explicit arguments error.
We will use the clos5 closure such as this:
clos5("Hello","Kara")
Since Hello is the first string in the parameter list, it is accessed with $0, and as Kara is the second string in the parameter list, it is accessed with $1. When we execute this code, we will see the message Hello Kara printed to the console.
This next example is used when the closure doesn't return any value. Rather than defining the return type as Void, we can use parentheses, as the following example shows:
let clos6: () -> () = {
print("Howdy")
}
In this example, we define the closure as () -> (). This tells Swift that the closure does not accept any parameters and also does not return a value. We will execute this closure such as this:
clos6()
As a personal preference, I am not very fond of this shorthand syntax. I think the code is much easier to ready when the void keyword is used rather than the parentheses.
We have one more shorthand closure example to demonstrate before we begin showing some really useful examples of closures. In this last example, we will demonstrate how we can return a value from the closure without the need to include the return keyword.
If the entire closure body consists of only a single statement, then we can omit the return keyword, and the results of the statement will be returned. Let's take a look at an example of this:
let clos7 = {
(first: Int, second: Int) -> Int in
first + second
}
In this example, the closure accepts two parameters of the Int type and will return an Int type. The only statement within the body of the closure adds the first parameter to the second parameter. However, if you notice, we do not include the return keyword before the addition statement. Swift will see that this is a single statement closure and will automatically return the results, just as if we put the return keyword before the addition statement. We do need to make sure the result type of our statement matches the return type of the closure.
All of the examples that were shown in the previous two sections were designed to show how to define and use closures. On their own, these examples did not really show off the power of closures and they did not show how incredibly useful closures are. The remainder of this article is written to demonstrate the power and usefulness of closures in Swift.
Using closures with Swift's array algorithms
Now that we have a better understanding of closures, let's see how we can expand on these algorithms using more advanced closures.
In this section, we will primarily be using the map algorithm for consistency purposes; however, we can use the basic ideas demonstrated with any of the algorithms. We will start by defining an array to use:
let guests = ["Jon", "Kim", "Kailey", "Kara"]
This array contains a list of names and the array is named guests. This array will be used for all the examples in this section, except for the very last ones.
Now that we have our guests array, let's add a closure that will print a greeting to each of the names in the guests array:
guests.map({
(name: String) -> Void in
print("Hello (name)")
})
Since the map algorithm applies the closure to each item of the array, this example will print out a greeting for each name within the guests array. After the first section in this article, we should have a pretty good understanding of how this closure works. Using the shorthand syntax that we saw in the last section, we could reduce the preceding example down to the following single line of code:
guests.map({print("Hello ($0)")})
This is one of the few times, in my opinion, where the shorthand syntax may be easier to read than the standard syntax.
Now, let's say that rather than printing the greeting to the console, we wanted to return a new array that contained the greetings. For this, we would have returned a string type from our closure, as shown in the following example:
var messages = guests.map({
(name:String) -> String in
return "Welcome (name)"
})
When this code is executed, the messages array will contain a greeting to each of the names in the guests array while the guests array will remain unchanged.
The preceding examples in this section showed how to add a closure to the map algorithm inline. This is good if we only had one closure that we wanted to use with the map algorithm, but what if we had more than one closure that we wanted to use, or if we wanted to use the closure multiple times or reuse them with different arrays. For this, we could assign the closure to a constant or variable and then pass in the closure, using its constant or variable name, as needed. Let's see how to do this. We will begin by defining two closures. One of the closures will print a greeting for each name in the guests array, and the other closure will print a goodbye message for each name in the guests array:
let greetGuest = {
(name:String) -> Void in
print("Hello guest named (name)")
}
let sayGoodbye = {
(name:String) -> Void in
print("Goodbye (name)")
}
Now that we have two closures, we can use them with the map algorithm as needed. The following code shows how to use these closures interchangeably with the guests array:
guests.map(greetGuest)
guests.map(sayGoodbye)
Whenever we use the greetGuest closure with the guests array, the greetings message is printed to the console, and whenever we use the sayGoodbye closure with the guests array, the goodbye message is printed to the console. If we had another array named guests2, we could use the same closures for that array, as shown in the following example:
guests.map(greetGuest)
guests2.map(greetGuest)
guests.map(sayGoodbye)
guests2.map(sayGoodbye)
All of the examples in this section so far have either printed a message to the console or returned a new array from the closure. We are not limited to such basic functionality in our closures. For example, we can filter the array within our closure, as shown in the following example:
let greetGuest2 = {
(name:String) -> Void in
if (name.hasPrefix("K")) {
print("(name) is on the guest list")
} else {
print("(name) was not invited")
}
}
In this example, we print out a different message depending on whether the name starts with the letter K or not.
As we mentioned earlier in the article, closures have the ability to capture and store references to any variable or constant from the context in which they were defined. Let's look at an example of this. Let's say that we have a function that contains the highest temperature for the last seven days at a given location, and this function accepts a closure as a parameter. This function will execute the closure on the array of temperature. The function can be written such as this:
func temperatures(calculate:(Int)->Void) {
var tempArray = [72,74,76,68,70,72,66]
tempArray.map(calculate)
}
This function accepts a closure defined as (Int)->Void. We then use the map algorithm to execute this closure for each item of the tempArray array. The key to using a closure correctly in this situation is to understand that the temperatures function does not know or care what goes on inside the calculate closure. Also, be aware that the closure is also unable to update or change the items within the function's context, which means that the closure cannot change any other variable within the temperature's function; however, it can update variables in the context that it was created in.
Let's look at the function that we will create the closure in. We will name this function testFunction. Let's take a look at the following code:
func testFunction() {
var total = 0
var count = 0
let addTemps = {
(num: Int) -> Void in
total += num
count += 1
}
temperatures(calculate: addTemps)
print("Total: (total)")
print("Count: (count)")
print("Average: (total/count)")
}
In this function, we begin by defining two variables named total and count, where both variables are of the Int type. We then create a closure named addTemps that will be used to add all of the temperatures from the temperatures function together. The addTemps closure will also count how many temperatures there are in the array. To do this, the addTemps closure calculates the sum of each item in the array and keeps the total in the total variable that was defined at the beginning of the function. The addTemps closure also keeps track of the number of items in the array by incrementing the count variable for each item. Notice that neither the total nor count variables are defined within the closure; however, we are able to use them within the closure because they were defined in the same context as the closure.
We then call the temperatures function and pass it the addTemps closure. Finally, we print the total, count, and average temperature to the console. When the testFunction is executed, we see the following output to the console:
Total: 498
Count: 7
Average: 71
As we can see from the output, the addTemps closure is able to update and use items that are defined within the context that it was created in, even when the closure is used in a different context.
Now that we have looked at using closures with the array map algorithm, let's look at using closures by themselves. We will also look at the ways we can clean up our code to make it easier to read and use.
Changing functionality
Closures also give us the ability to change the functionality of classes on the fly. With closures, we are able to write functions and classes whose functionality can change, based on the closure that is passed into it as a parameter. In this section, we will show how to write a function whose functionality can be changed with a closure.
Let's begin by defining a class that will be used to demonstrate how to swap out functionality. We will name this class TestClass:
class TestClass {
typealias getNumClosure = ((Int, Int) -> Int)
var numOne = 5
var numTwo = 8
var results = 0
func getNum(handler: getNumClosure) -> Int {
results = handler(numOne,numTwo)
return results
}
}
We begin this class by defining a type alias for our closure that is namedgetNumClosure. Any closure that is defined as a getNumClosure closure will take two integers and return an integer. Within this closure, we assume that it does something with the integers that we pass in to get the value to return, but it really doesn't have to. To be honest, this class doesn't really care what the closure does as long as it conforms to the getNumClosure type. Next, we define three integers that are named numOne, NumTwo, and results.
We also define a method named getNum(). This method accepts a closure that confirms the getNumClosure type as its only parameter. Within the getNum() method, we execute the closure by passing in the numOne and numTwo class variables, and the integer that is returned is put into the results class variable.
Now, let's look at several closures that conform to the getNumClosure type that we can use with the getNum() method:
var max: TestClass.getNumClosure = {
if $0 > $1 {
return $0
} else {
return $1
}
}
var min: TestClass.getNumClosure = {
if $0 < $1 {
return $0
} else {
return $1
}
}
var multiply: TestClass.getNumClosure = {
return $0 * $1
}
var second: TestClass.getNumClosure = {
return $1
}
var answer: TestClass.getNumClosure = {
var tmp = $0 + $1
return 42
}
In this code, we define five closures that conform to the getNumClosure type:
max: This returns the maximum value of the two integers that are passed in
min: This returns the minimum value of the two integers that are passed in
multiply: This multiplies both the values that are passed in and returns the product
second: This returns the second parameter that was passed in
answer: This returns the answer to life, the universe, and everything
In the answer closure, we have an extra line that looks like it does not have a purpose: var tmp = $0 + $1. We do this purposely because the following code is not valid:
var answer: TestClass.getNumClosure = {
return 42
}
This class gives us the error: contextual type for closure argument list expects 2 arguments, which cannot be implicitly ignored error. As we can see by the error, Swift does not think that our closure accepts any parameters unless we use $0 and $1 within the body of the closure. In the closure named second, Swifts assumes that there are two parameters because $1 specifies the second parameter.
We can now pass each one of these closures to the getNum method of our TestClass to change the functionality of the function to suit our needs. The following code illustrates this:
var myClass = TestClass()
myClass.getNum(handler: max)
myClass.getNum(handler: min)
myClass.getNum(handler: multiply)
myClass.getNum(handler: second)
myClass.getNum(handler: answer)
When this code is run, we will receive the following results for each of the closures:
max: results = 8
min: results = 5
multiply: results = 40
second: results = 8
answer: results = 42
The last example we are going to show you in this article is one that is used a lot in frameworks, especially the ones that have a functionality that is designed to be run asynchronously.
Selecting a closure based on results
In the final example, we will pass two closures to a method, and then depending on some logic, one, or possibly both, of the closures will be executed. Generally, one of the closures is called if the method was successfully executed and the other closure is called if the method failed.
Let's start off by creating a class that will contain a method that will accept two closures and then execute one of the closures based on the defined logic. We will name this class TestClass. Here is the code for the TestClass class:
class TestClass {
typealias ResultsClosure = ((String) -> Void)
func isGreater(numOne: Int, numTwo:Int, successHandler:
ResultsClosure, failureHandler: ResultsClosure) {
if numOne > numTwo {
successHandler("(numOne) is greater than (numTwo)")
}
else {
failureHandler("(numOne) is not greater than (numTwo)")
}
}
}
We begin this class by creating a type alias that defines the closure that we will use for both the successful and failure closures. We will name this type alias ResultsClosure. This example also illustrates why to use a type alias, rather than retyping the closure definition. It saves us a lot of typing and also prevents us from making mistakes. In this example, if we did not use a type alias, we would need to retype the closure definition four times, and if we needed to change the closure definition, we would need to change it in four spots. With the type alias, we only need to type the closure definition once and then use the alias throughout the remaining code.
We then create a method named isGreater that takes two integers as the first two parameters and then two closures as the next two parameters. The first closure is named successHandler, and the second closure is named failureHandler. Within the isGreater method, we check whether the first integer parameter is greater than the second one. If the first integer is greater, the successHandler closure is executed; otherwise, the failureHandler closure is executed.
Now, let's create two of our closures. The code for these two closures is as follows:
var success: TestClass. ResultsClosure = {
print("Success: ($0)")
}
var failure: TestClass. ResultsClosure = {
print("Failure: ($0)")
}
Note that both closures are defined as the TestClass.ResultsClosure type. In each closure, we simply print a message to the console to let us know which closure was executed. Normally, we would put some functionality in the closure.
We will then call the method with both the closures such as this:
var test = TestClass()
test.isGreater(numOne: 8, numTwo: 6, successHandler:success,
failureHandler:failure)
Note that in the method call, we are sending both the success closure and the failure closure. In this example, we will see the message, Success: 8 is greater than 6. If we reversed the numbers, we would see the message, Failure: 6 is not greater than 8. This use case is really good when we call asynchronous methods, such as loading data from a web service. If the web service call was successful, the success closure is called; otherwise, the failure closure is called.
One big advantage of using closures such as this is that the UI does not freeze while we wait for the web service call to complete. As an example, try to retrieve data from a web service such as this:
var data = myWebClass.myWebServiceCall(someParameter)
Our UI would freeze while we wait for the response to come back, or we would have to make the call in a separate thread so that the UI would not hang. With closures, we pass the closures to the networking framework and rely on the framework to execute the appropriate closure when it is done. This does rely on the framework to implement concurrency correctly to make the calls asynchronously, but a decent framework should handle that for us.
Creating strong reference cycles with closures
Earlier in this article, we said, the best thing is, for the most part, Swift will handle the memory management for us. The for the most part section of the quote means that if everything is written in a standard way, Swift will handle the memory management of the closures for us. However, there are times where memory management fails us. Memory management will work correctly for all of the examples that we have seen in this article so far. It is possible to create a strong reference cycle that prevents Swift's memory management from working correctly. Let's look at what happens if we create a strong reference cycle with closures.
A strong reference cycle may happen if we assign a closure to a property of a class instance and within that closure, we capture the instance of the class. This capture occurs because we access a property of that particular instance using self, such as self.someProperty, or we assign self to a variable or constant, such as let c = self. By capturing a property of the instance, we are actually capturing the instance itself, thereby creating a strong reference cycle where the memory manager will not know when to release the instance. As a result, the memory will not be freed correctly.
Let's begin by creating a class that has a closure and an instance of the String type as its two properties. We will also create a type alias for the closure type in this class and define a deinit() method that prints a message to the console. The deinit() method is called when the class gets released and the memory is freed. We will know when the class gets released when the message from the deinit() method is printed to the console. This class will be named TestClassOne. Let's take a look at the following code:
class TestClassOne {
typealias nameClosure = (() -> String)
var name = "Jon"
lazy var myClosure: nameClosure = {
return self.name
}
deinit {
print("TestClassOne deinitialized")
}
}
Now, let's create a second class that will contain a method that accepts a closure that is of the nameClosure type that was defined in the TestClassOne class. This class will also have a deinit() method, so we can also see when it gets released. We will name this class TestClassTwo. Let's take a look at the following code:
class TestClassTwo {
func closureExample(handler: TestClassOne.nameClosure) {
print(handler())
}
deinit {
print("TestClassTwo deinitialized")
}
}
Now, let's see this code in action by creating instances of each class and then trying to manually release the instance by setting them to nil:
var testClassOne: TestClassOne? = TestClassOne()
var testClassTwo: TestClassTwo? = TestClassTwo()
testClassTwo?.closureExample(testClassOne!.myClosure)
testClassOne = nil
print("testClassOne is gone")
testClassTwo = nil
print("testClassTwo is gone")
What we do in this code is create two optionals that may contain an instance of our two test classes or nil. We need to create these variables as optionals because we will be setting them to nil later in the code so that we can see whether the instances are released properly.
We then call the closureExample() method of the TestClassTwo instance and pass it the myClosure property from the TestClassOne instance. We now try to release the TestClassOne and TestClassTwo instances by setting them to nil. Keep in mind that when an instance of a class is released, it attempts to call the deinit() method of the class, if it exists. In our case, both classes have a deinit() method that prints a message to the console, so we know when the instances are actually released.
If we run this project, we will see the following messages printed to the console:
testClassOne is gone
TestClassTwo deinitialized
testClassTwo is gone
As we can see, we do attempt to release the TestClassOne instances, but the deinit() method of the class is never called, indicating that it was not actually released; however, the TestClassTwo instance was properly released because the deinit() method of that class was called.
To see how this is supposed to work without the strong reference cycle, change the myClosure closure to return a string type that is defined within the closure itself, as shown in the following code:
lazy var myClosure: nameClosure = {
return "Just Me"
}
Now, if we run the project, we should see the following output:
TestClassOne deinitialized
testClassOne is gone
TestClassTwo deinitialized
testClassTwo is gone
This shows that the deinit() methods from both the TestClassOne and TestClassTwo instances were properly called, indicating that they were both released properly.
In the first example, we capture an instance of the TestClassOne class within the closure because we accessed a property of the TestClassOne class using self.name. This created a strong reference from the closure to the instance of the TestClassOne class, preventing memory management from releasing the instance.
Swift does provide a very easy and elegant way to resolve strong reference cycles in closures. We simply need to tell Swift not to create a strong reference by creating a capture list. A capture list defines the rules to use when capturing reference types within a closure. We can declare each reference to be a weak or un owned reference rather than a strong reference.
A weak keyword is used when there is the possibility that the reference will become nil during its lifetime; therefore, the type must be an optional. The unowned keyword is used when there is not a possibility of the reference becoming nil.
We define the capture list by pairing the weak or unowned keywords with a reference to a class instance. These pairings are written within square brackets ([ ]). Therefore, if we update the myClosure closure and define an unowned reference to self, we should eliminate the strong reference cycle. The following code shows what the new myClosure closure will look similar to:
lazy var myClosure: nameClosure = {
[unowned self] in
return self.name
}
Notice the new line—[unowned self] in. This line says that we do not want to create a strong reference to the instance of self. If we run the project now, we should see the following output:
TestClassOne deinitialized
testClassOne is gone
TestClassTwo deinitialized
testClassTwo is gone
This shows that both the TestClassOne and TestClassTwo instances were properly released.
Summary
In this article, we saw that we can define a closure just like we can define an Int or String type. We can assign closures to a variable, pass them as an argument to functions, and also return them from functions.
Closures capture a store references to any constants or variables from the context in which the closure was defined. We have to be careful with this functionality to make sure that we do not create a strong reference cycle, which would lead to memory leaks in our applications.
Swift closures are very similar to blocks in Objective-C, but they have a much cleaner and eloquent syntax. This makes them a lot easier to use and understand.
Having a good understanding of closures is vital to mastering the Swift programming language and will make it easier to develop great applications that are easy to maintain. They are also essential for creating first class frameworks that are both easy to use and maintain.
The three use cases that we saw in this article are by no means the only three useful uses for closures. I can promise you that the more you use closures in Swift, the more uses you will find for them. Closures are definitely one of the most powerful and useful features of the Swift language, and Apple did a great job by implementing them in the language.
Resources for Article:
Further resources on this subject:
Revisiting Linux Network Basics [article]
An Introduction to the Terminal [article]
Veil-Evasion [article]
Read more