Skip to main content
Kelvas blog
  1. Posts/

Swift: @escaping

·850 words·4 mins

Do you know the @escaping statement in Swift? The compiler creates an error asking you to add it but you don’t know why?

Don’t worry, we’ll figure it out together.

The notion of asynchronism #

With the latest versions of Swift, it is possible to use the async and await keywords to handle asynchronism in our application. But if you have coded applications with older versions of Swift, you probably use closures/callbacks for your asynchronous methods:

func getData(closure: @escaping ((_ data: Data?, _ error: Error?)->())) {
    ...
}

In this piece of code we’re going to call a method that is itself asynchronous to retrieve remote data and therefore with a certain delay.

If we use URLSession we could have a code like this one:

import Foundation

func getData(closure: ((_ data: String?, _ error: Error?)->())) {
    let url = URL(string: "https://www.myWebsite.com/myApi")!
    let task = URLSession.shared.dataTask(with: url) { data, response, error in
        if let error = error {
            closure(nil, error)
            return
        }
        if let data = data, let string = String(data: data, encoding: .utf8) {
            closure(string, nil)
        }
    }
    task.resume()
}

But why @escaping? #

You note that in the above code it is necessary to use @escaping without which the compiler displays this error:

Image

But then how does this call differ from this one:

func filter(values: [String], closure: ((_ value: String) -> Bool)) -> [String] {
    var result: [String] = []
    for value in values {
        let isValid = closure(value)
        if isValid {
            result.append(value)
        }
    }
    return result
}

In both cases we use a closure but then why in one case we need @escaping and not in the other?

The scope #

In the case of our backward method the closure is not marked as @escaping because the closure is used directly by our method. It does not change scope.

On the other hand it is the case for our method getData. We give our closure to another scope. This one will change scope. We then say that it escapes from its scope because it continues to exist after the method has been run.

The compiler then needs to know that the closure will leave its original scope and that we know how to manage the particular cases that result from this: access to the self variable within the closure for example.

The case of self in an escaped closure #

As we said before, a closure noted @escaping will continue to exist after the end of the method.

Now a closure is simply an anonymous method owned by a class and which can therefore call the self variable at will.

But at what risk?

Unlike a non-escaped closure, it is necessary to specify self before any method or property belonging to the class containing the closure.

Why is this?

Well, simply because self becomes a reference to the scope and must be retained, this is what we call strong references.

Let’s take a simple example:

classDiagram class RecipeViewModel { + onRefresh() } class RecipeRepository { + getRecipes(closure: @escaping ...) } RecipeViewModel "0" --> "1" RecipeRepository: own

The RecipeViewModel class therefore has an instance of the RecipeRepository class. The latter is responsible for communicating with the API to retrieve the data we are interested in.

Let’s start by defining our repository:

final class RecipeRepository {
    
    func getData(closure: @escaping ((_ data: String?, _ error: Error?)->())) {
        let url = URL(string: "https://www.myWebsite.com/myApi")!
        let task = URLSession.shared.dataTask(with: url) { data, response, error in
            if let error = error {
                closure(nil, error)
                return
            }
            if let data = data, let string = String(data: data, encoding: .utf8) {
                closure(string, nil)
            }
        }
        task.resume()
    }
    
}
Information Note that the closure is noted @escaping.

When calling the onRefresh method of the viewModel, the data will be retrieved:

final class RecipeViewModel {
    
    private let repository: RecipeRepository
    
    init(repository: RecipeRepository) {
        self.repository = repository
    }
    
    func onRefresh() {
        repository.getData { (data: String?, error: Error?) in
            guard let data = data else {
                self.displayError()
                return
            }
            self.updateUI(with: data)
        }
    }
    
    private func displayError() {
        //    Do something
    }
    
    private func updateUI(with data: String) {
        //    Do something
    }
    
}

As you can see we use self in the closure. This is a strong reference to our closure, so this element must not be deallocated until the end of the closure. RecipeViewModel becomes a strong reference to RecipeRepository.

As it is, we master both the RecipeRepository and RecipeViewModel classes, so it is very easy to manage the life cycle of these objects. This is much less obvious when using a class that does not belong to you.

This notion of strong reference can be avoided / bypassed by the weak and unowned keywords that we will see in a next article!

Conclusion #

As you can see, it is quite easy to add a simple @escaping without properly handling all the consequences of this addition.

If possible, I advise you to use async / await to stay as up-to-date as possible. We will have the opportunity to see this in a future article. If you want to know more about weak et unowned you can read my other post here.