How do closures work on Swift?

Asked

Viewed 136 times

2

Perhaps this is one of the most difficult concepts to understand for those who are beginning in language. I’ve seen some definitions but so far I can’t understand.

Could someone give an explanation with examples?

1 answer

2


In fact we have already explained this in several posts about the closure in itself and the anonymous function that is related and important concept:

Follow the links in the above posts to understand the whole concept (I realize that many people have trouble understanding why they don’t want to learn all the details).

So I’ll summarize here because almost everything has been said in detail before.

Anonymous function

The correct term for the basic mechanism.

The anonymous function is the unnamed function, which is that function which is associated with an object through a local variable, including the parameter or an object (instance or type). So you can go carrying the function to all corners of the application as if the function were a given, any value.

This gives a very great flexibility and using creativity can become a powerful mechanism of customization of objects and methods to run some algorithm that will be defined elsewhere. Then you can write a universal code at a certain point in the application, but that part of the execution will be defined at another point. This part will be known through an anonymous function, usually written with a syntax of lambda.

Swift allows this because the function is first class.

The anonymous function is just a normal function that has an extra indirect through a pointer so the function functions as an object, it is treated as if it were a given.

Interestingly it seems that Swift treats normal anonymous function as closure even if it does not capture. It may be because internally it maintains a capture mechanism even if the capture is not made. Strictly the term is wrong in this context, Swift has its own reinventions, which is not exactly bad, but seems very expensive from Apple. The true enclosure only occurs in the example I will put below, in the other examples nothing is enclosed even if the documentation says so.

I’m not sure if the AP understands what this mechanism is, it itself is not so complicated.

Closure

It turns out that at the moment you define an anonymous function it may be that you use a value that is contained in a function variable where the anonymous function is being written. We said that the anonymous function captures this value. So if you pass this function to another place (and it only makes sense to have an anonymous function if you do that) the value that was in that captured variable goes along with the function (I’m not going to go into the implementation detail of how this is done, but there are important implications).

Obviously only in some cases this is necessary, it depends on the code you are writing inside the function. If you do not use some variable that was in the scope of the function where the anonymous function was defined so there is no enclosure occurring.

I’m talking about defining a function within another that is the most common, but could define in a field as well, in which case the captured object will always be the self.

When the anonymous function is used in it at some point the variable is used in the algorithm and the value that the variable is valid will be used.

One of the things that I think is not simple to understand is about this variable continuing to exist even after you are no longer in the function where it existed. In fact the capture of the variable will enclose the variable within the anonymous function, then the lifetime of that variable is extended and equals the lifetime of the anonymous function.

The variable that would probably be in stack passes to the heap to maintain the necessary life span. As in heap everything is stored by reference so usually any change in the variable made anywhere will happen there in the heap and this may bring surprises that I will not speak here, but already has answers about (another). See more.

Example

So let’s take the example to demonstrate this (I’ll take from documentation).

func makeIncrementer(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementer() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementer
}
let incrementBySeven = makeIncrementer(forIncrement: 7)
print(incrementBySeven())
print(incrementBySeven())

Behold working in the ideone. And in the repl it.. Also put on the Github for future reference.

Let’s understand what happens there.

makeIncrementer() is a normal function. Its responsibility is to create an anonymous function to be used elsewhere. We can see that there is a parameter with a numerical value.

Inside there is a variable called runningTotal. She’s important because she’ll be captured.

Then we set the call anonymous function incrementer. Note that it is a variable like any other within makeIncrementer(), so much so that the last line of the function there is a return incrementer, therefore is returning the object of this variable, which is a function, which has the ability to execute something and capture local variables.

Within the anonymous function an account is made using the local variable runningTotal and the variable amount which is a parameter. Both are captured by the anonymous function. Then they will have extended lifespan and these variables will exist as long as the anonymous function defined there continues to exist. So you can access your values for all this time.

Next we see a normal code that calls makeIncrementer passing as argument 7, then the variable incrementBySeven shall have the object that makeIncrementer return. We know that it is returning an anonymous function that has two captured variables.

Then we have the function executed incrementBySeven. Yes, this variable is a function now because its object is a function, we can call it. And the code is executed.

How the code is being executed the first time the value of runningTotal It’s 0, as we know from the code. And it will take that value and add up as the argument we use, then 7. The return is the result of that sum, so print 7.

Now we have executed exactly the same thing. As now is the second time it executes the variable runningTotal is not worth 0, it is worth 7. Remember that the variable is still alive within the anonymous function that still exists. Then the value of runningTotal now is 7, and as more a sum of 7 the result will be 14.

You can call it another time, and it’ll always be that way. We will only start from 0 again when we create another object with this anonymous function, because then everything starts from 0. This is only possible because the life time of the variable became global.

Completion

The idea of the anonymous function without the enclosure is actually much simpler because you see the parameterization and have only one concept. Just to leave an example of the documentation:

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
    return s1 > s2
})

There you have a function being created that will be passed to the function sorted() which will make the data classification and at a given time will use its function to establish the order. Your function will be executed inside every step you need to decide if something is in order or needs to change position.

Swift has a lot of detail and variations of use of the mechanism, but then it becomes a chapter of a book to talk about everything, would have to ask more specific questions.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.