D80's introduction to @escaping left me with so many questions. I've spent the past several days trying to answer them. This is what I've learned so far.
1/
#100DaysOfSwiftUI
1/
#100DaysOfSwiftUI
First, when do we use the @escaping attribute and why?
The Swift compiler is smart. If it notices that our function includes an escaping closure, it forces us to label that closure in the function definition as @escaping. If we don't, we get an error and it won't compile.
2/
The Swift compiler is smart. If it notices that our function includes an escaping closure, it forces us to label that closure in the function definition as @escaping. If we don't, we get an error and it won't compile.
2/
What is an escaping closure?
This is a closure that may be executed after the function that received it has returned. The closure is 'outliving' that function.
So why does the Swift compiler insist that we label functions that include escaping closures?
3/
This is a closure that may be executed after the function that received it has returned. The closure is 'outliving' that function.
So why does the Swift compiler insist that we label functions that include escaping closures?
3/
Two reasons:
1. Escaping closures are a little dangerous. The compiler wants us to acknowledge this and consider what we're doing.
2. Labeling the function allows the compiler to communicate the presence of an escaping closure to callers of that function.
4/
1. Escaping closures are a little dangerous. The compiler wants us to acknowledge this and consider what we're doing.
2. Labeling the function allows the compiler to communicate the presence of an escaping closure to callers of that function.
4/
Why are escaping closures a little dangerous?
Because if we're not careful they can lead to memory leaks.
And what is a memory leak?
A memory leak is when something doesn't get cleared from memory. If this happens too much, our program can stop working properly.
5/
Because if we're not careful they can lead to memory leaks.
And what is a memory leak?
A memory leak is when something doesn't get cleared from memory. If this happens too much, our program can stop working properly.
5/
So why do memory leaks happen?
To understand this it helps to understand a little bit about memory management. With iOS, the OS handles memory management for us.
So if an instance of a reference type is no longer needed, iOS removes it from memory.
6/
To understand this it helps to understand a little bit about memory management. With iOS, the OS handles memory management for us.
So if an instance of a reference type is no longer needed, iOS removes it from memory.
6/
How does iOS know if the instance is no longer needed?
It uses Automatic Reference Counting (ARC). ARC keeps track of how many references there are to an object. When that count reaches zero, iOS removes it from memory. It won't remove an object if the count is > zero.
7/
It uses Automatic Reference Counting (ARC). ARC keeps track of how many references there are to an object. When that count reaches zero, iOS removes it from memory. It won't remove an object if the count is > zero.
7/
So then why do memory leaks happen?
Memory leaks happen because of something called a retain cycle. That's when two objects reference each other preventing the count from reaching zero.
It's best to look at an example from the Swift documentation.
8/
Memory leaks happen because of something called a retain cycle. That's when two objects reference each other preventing the count from reaching zero.
It's best to look at an example from the Swift documentation.
8/
I'm going to walk us through it, but you can check out the docs here: https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html
So we have two classes: a Person class and an Apartment class.
9/
So we have two classes: a Person class and an Apartment class.
9/
And we define two variables: john and unit4A.
We create instances of Person and Apartment and assign them to our variables.
Finally we link the instances so the person has an apartment and the apartment has a tenant.
10/
We create instances of Person and Apartment and assign them to our variables.
Finally we link the instances so the person has an apartment and the apartment has a tenant.
10/
Here's what all of that looks like. 'Strong' means strong reference. We'll get into the other types soon enough. For now, just note that strong references are the default.
11/
11/
So if we set var john and var unit4a to nil, that removes the references between the variables and their respective instances.
We now have a memory leak. iOS will not remove those instances from memory because each still has a reference count of one — it's counterpart.
12/
We now have a memory leak. iOS will not remove those instances from memory because each still has a reference count of one — it's counterpart.
12/
So how do we solve this?
We can use a 'weak reference'. Weak references differ from strong ones in a couple of important ways:
1. Weak references don't count towards ARC's reference count.
2. They are optionals.
13/
We can use a 'weak reference'. Weak references differ from strong ones in a couple of important ways:
1. Weak references don't count towards ARC's reference count.
2. They are optionals.
13/
Back to our example, we mark the tenant variable in the Apartment class as weak. Only one of apartment and tenant should be marked as weak. We choose tenant because an apartment without a tenant can be expected.
14/
14/
So now if we set var john and var unit4a to nil, the Person instance will have no strong references to it. The count is zero & iOS removes it from memory. With the Person instance gone, the Apartment instance has no references to it and it is removed as well. Cool right?!
15/
15/
You can also solve the retain cycle problem by using 'unowned references'. These save you from using optionals but they can only be used in certain circumstances. You can read more about them in Swift's ARC documentation linked above.
Let's get back to escaping closures.
16/
Let's get back to escaping closures.
16/
This memory management discussion has been about class instances so far, but it applies to closures as well. That's because, like classes, closures are reference types too.
Let's check out an example of how an escaping closure can cause a memory leak.
17/
Let's check out an example of how an escaping closure can cause a memory leak.
17/
This is my modified version of an example from the Swift documentation page on closures https://docs.swift.org/swift-book/LanguageGuide/Closures.html
So SomeClass has a property x and a method doSomething that calls a function that takes a closure. That function adds the closure to an array (completionHandlers)
18/
So SomeClass has a property x and a method doSomething that calls a function that takes a closure. That function adds the closure to an array (completionHandlers)
18/
Once we call the doSomething method on the instance, we've created a retain cycle. Even if we remove var instance's and var completionHandler's strong references to the class instance and closure respectively (lines 8 & 9), the instance and closure remain in memory.
19/
19/
Just like our apartment example, we can solve this retain cycle by creating a weak reference. Here's what that syntax looks like with a closure.
The part inside the square brackets is called a 'capture list'.
20/
The part inside the square brackets is called a 'capture list'.
20/
With that change there are no strong references to our instance and iOS removes it from memory.
Let's wrap this up.
21/
Let's wrap this up.
21/
Why does @escaping exist? Because as it says on the Swift doc About page, Swift is a safe language.
It's designed to get us to consider the implications of our code. Here: the potential for memory leaks.
22/
It's designed to get us to consider the implications of our code. Here: the potential for memory leaks.
22/
Once you understand this about Swift, you start to see it everywhere.
With optionals, and as I've written before, with error handling too.
https://twitter.com/TheKumano/status/1266979254580830208?s=20
23/
With optionals, and as I've written before, with error handling too.
https://twitter.com/TheKumano/status/1266979254580830208?s=20
23/
The previous thread wouldn't have been possible without the writing of @DonnyWals, @andrewcbancroft, and @stablekernel. Thank you for sharing.
Bibliography:
(1) https://www.donnywals.com/what-is-escaping-in-swift/
(2) https://www.andrewcbancroft.com/2017/05/11/why-do-we-need-to-annotate-escaping-closures-in-swift/
(3) https://stablekernel.com/article/how-to-prevent-memory-leaks-in-swift-closures/
All errors are my own.
Bibliography:
(1) https://www.donnywals.com/what-is-escaping-in-swift/
(2) https://www.andrewcbancroft.com/2017/05/11/why-do-we-need-to-annotate-escaping-closures-in-swift/
(3) https://stablekernel.com/article/how-to-prevent-memory-leaks-in-swift-closures/
All errors are my own.