Swift is a very powerful programming language and is the first choice for developing applications for the Apple ecosystem; IOS, macOS, watchOS and tvOS. As developers who code with Swift, we often use closures; An important and important chapter of the language.
Closures are not a beginner’s topic. However, it is something that everyone must learn soon. There are many aspects to understand and understand how they work. In all of these, there is a particular; Escaping closures and the @escaping attribute. In this article, I will explain as simply as possible what they are and their possible spin-offs.
Escaped and non-escaped closures
When we talk about escape closures, we always refer to closures provided as parameters to functions or methods. In general, we classify closures provided to methods (or functions) into two categories:
- After method execution completesbeforeClosure of the call.
- After method execution completesafterClosure of the call.
In the latter case, we are talking about escape closures; Turning off the xecution method should continue even after the electrons live until we call them at any later time in the future.
In the former case, contrary to what I described above, we call closures non-escaping.
Until Swift 3, all closures passed as parameters to a method or function were considered escaped by default. Since Swift 3, this is no longer true; By default, all methods are considered unescaped, which means they are called before the method execution is complete.
The following code section demonstrates a non-escaped closure:
func add(num1: Double,
num2: Double,
completion: (_ result: Double) -> Void) {
let sum = num1 + num2
completion(sum)
}
Copy the code
The Completion closure previously executes the method called by the code leaf, so this is not a case of escaping closure.
However, how does a closure escape from a method so that we end up with the opposite of the above?
Escape method
In order for a closure to be an escaped closure, it is necessary to keep references to it out of the scope of the method so that we can use it later. Look at the following code:
class Demo {
var result: Double?
var resultHandler: (() -> Void)?
func add2(num1: Double,
num2: Double,
completion: () -> Void) {
resultHandler = completion
result = num1 + num2
}
}
Copy the code
Here we have a result property, which holds the result of the addition that occurred inside the method. But we also have property in resultHandler; This keeps a reference to the closure provided by Completion as a method parameter.
Closures are escaped from methods with the following line:
resultHandler = completion
Copy the code
However, this is not the only operation required, so we can say that completion is an escape closure. We must specify the compiler explicitly, otherwise we will see the following error in Xcode:
To fix it, we need to tag the closure with the @escaping attribute. We place this property after the closure name and semicolon, but before the closure type, as follows:
func add2(num1: Double,
num2: Double,
completion: @escaping () -> Void) {
...
}
Copy the code
The compiler no longer complains, and Completion is now officially an escape closure.
Put escape close into action
Let’s add two more methods to the Demo class above; One will call the add2(num1:num2: Completion 🙂 method, and the other will call the resultHandler closure to get the final result:
class Demo { ... func doubleSum(num1: Double, num2: Double) { add2(num1: num1, num2: num2) { guard let result = self.result else { return } self.result = result * 2 } } func getResult() { resultHandler? ()}}Copy the code
The first method doubles the result of the add method. However, the result is not doubled, and the code inside the closure body of the getResult() method is not executed until we call the method add2(num1:num2:completion:).
This is where we benefit from escaping closures; We can trigger closure calls when we need them in our code and when it suits us. Although the examples provided are deliberately simplistic, in real projects the practical advantages become more obvious and bold.
Note the strong reference period
Let’s add one last one to the Demo class and implement the default initializer and destructor methods:
class Demo {
init() {
print("Init")
}
deinit {
print("Deinit")
}
...
}
Copy the code
Init () is the first method called when the Demo initializes the instance, and deinit is the last method called before the instance is released. I added a print command to each of them to verify that they were called and that there were no memory leaks when using methods with the escape closure described above.
Note: When we try to release an object by setting it to nil, there may be a memory leak, but the object remains in memory because there is a strong reference between the object and other objects that keep it active.
Now, let’s add the following lines to use all of the above:
var demo: Demo? = Demo() demo? .doubleSum(num1: 5, num2: 10) demo? .getResult() print((demo? .result!) !). demo = nilCopy the code
First, we initialize an optional instance of the class, Demo, so that we can set it to nil later. We then call the doubleSum(num1:num2:) method to add the two numbers given as arguments, and then double the result. However, as I said earlier, this does not happen until we call the getResult() method; The method that actually calls the escape closure add2(num1:num2:completion:).
Finally, we print the value demo of the Result property in the instance and set it demo to nil.
* Note: * As shown in the code snippet above, use an exclamation mark (!) Forcing optional values to expand is a very bad practice; please don’t do it. The only reason I’m doing this here is to keep things as simple as possible.
The above line will print the following:
Init
30.0
Copy the code
Notice that the “Deinit” message is missing! That is, deinit does not call this method, proving that making demo instance nil has no real result. It looks like we managed to fix the memory leak with just a few simple lines of code.
The reason behind the memory leak
Before we can figure out how to fix a memory leak, it’s important to understand why it happens. To find it, let’s take a few steps back and modify what we did before.
First, we use the following Completion execution closure to escape from the method:
resultHandler = completion
Copy the code
This line is even more “guilty” than it seems, because it creates a strongly referenced completion to the closure.
Note: Closures are reference types, just like classes.
However, this alone is not enough to cause a problem, because releasing the demo instance removes references to closures. The real trouble starts with the closure body inside the doubleSum(num1:num2:) method.
There, we create another strong reference from the closure to the demo instance this time by capturing ** objects when using object access properties: selfResult
guard let result = self.result else { return }
self.result = result * 2
Copy the code
When they are all in place, the Demo instance retains a strong reference to the closure, which in turn is a strong reference to the instance. This creates a retention loop, also known as a strong reference loop. When this happens, each reference type keeps the other active in memory, so none of them are eventually freed.
Note that this only happens in classes that contain methods with escaped closures. Structs are different because they are not references but value types, and an explicit reference to self is not mandatory.
Eliminate strong reference loops
There are two ways to avoid strong reference loops and thus memory leaks. The first is to manually and explicitly release the reference to the closure after we call it:
func getResult() { resultHandler? () // Setting nil to resultHandler removes the reference to closure. resultHandler = nil }Copy the code
The second method weakly captures the self instance in the body of the closure:
func doubleSum(num1: Double, num2: Double) { add2(num1: num1, num2: num2) { [weak self] in guard let result = self? .result else { return } self? .result = result * 2 } }Copy the code
[weak self] in View add after closing. With this, we set up a weak reference to the Demo instance, so we avoid the retention loop. Notice that we use self as an optional value followed by a question mark (?) Symbols.
Neither of these changes is necessary to avoid strong reference loops. Whichever one we ultimately choose, from now on, the output will also contain the “Deinit” message. This means that the demo object becomes nil, and we no longer have a memory leak.
30.0 Deinit InitCopy the code
summary
One thing to take with you out of here is to be careful when using escape closures. Whether you implement your own method that accepts escaped closures as arguments or use an API with escaped closures, always make sure you don’t end up with a strong reference loop. Memory leaks are a big no-no when developing applications, and we certainly don’t want our applications to crash at some point or be terminated by the system. On the other hand, don’t hesitate to use escape closures; They provide the advantage of generating more powerful code.
Anyway, I’ve tried to introduce the subject as simply as possible, and I hope you enjoy it, and that you’ll find something new to learn from it. Thank you for reading!