Swift Programming

Swift Closures: @escaping vs @non-escaping

Anurag Ajwani
3 min readApr 5, 2023

For this post I assume you are familiar with the basics of Swift programming.

In Swift, Closures are self-contained blocks of functionality–lines of code to be executed in group. In simple they are akin to functions that require no names. They be stored in properties and be passed around. Let’s look at an example:

// storing closures in properties
let myClosure = {
// do something
}

func expectingClosure(onFinishedExecution onFinished: () -> ()) {
// do something
onFinished()
}

class StoringClosure {
let propA: () -> ()

init(propA: @escaping () -> ()) {
self.propA = propA
}

func doSomething() {
self.propA()
}
}

Notice the StoringClosure’s initialisation requires a closure which stored within the instance’s propA property. Notice also that the property is marked with @escaping whereas the other examples do not. Why is that?

Escaping closures

In Swift, a closure is said to be escaping if it is passed as an argument an stored elsewhere during the execution of that function. In the example we had a closure being provided as parameter of the initialisation function of the StoringClosure class. The closure was stored within the class.

Closures that are provided to other functionality outside the bound of a function is also escaping. Take a look at the following example:

class BooksAPI {
let api: API

init(api: API) {
self.api = api
}

func fetchBooks(onFetch: @escaping ([Book]) -> ()) {
self.api.get("/books", resultHandler: { (result: Result<Data, Error>) in
switch result {
case .success(data):
let books = // parse json data
onFetch(books)
case .error:
// handle error
}
})
}
}

In the example above even though the closure wasn’t stored within the BooksAPI class it still needed to be marked as @escaping as the closure was used within another closure. The onFetch closure escaped the bounds of the fetchBooks function. And thus the onFetch closure can outlive the execution of the fetchBooks function.

Risks of escaping closures

Code within closures can make use of properties and function in the context that they are used.

class CachedBookRepository: BookRepository {
private var books: [Book] = []
private var bookRepository: BookRepository // api book repository

init(bookRepository: BookRepository) {
self.bookRepository = bookRepository
}

func getBook(withISBN isbn: Int, completion: @escaping (Result<Book, Error>) -> Void) {
if let book = books.first(where: { $0.isbn == isbn }) {
completion(.success(boo))
return
}
self.bookRepository.getBook(withISBN: isbn, completion: { result in // second closure
switch result {
case .success(let book):
self.books.append(book) // saves new book
completion(.success(book))
case .failure(let erorr):
completion(.failure(error))
}
})
}
}

In the example above we are caching books in memory. We first enquire the in-memory cache. If the book is present we call the completion closure with a success value. However if the book is not present within the cache we enquire the api book repository. When calling the api book repository we provide a closure which saves new books in the books property. In this case this second closure has a strong reference to the CachedBookRepository instance.

What happens when the user dismisses the Book search screen that owns the instance of this repository during a book search? The second closure makes use of self.books and thus holds a strong reference to the CachedBookRepository instance. In such case we are creating a retain cycle and the stopping the instance being cleared away from memory.

There are ways around this such marking any reference within the closure as weak i.e.:

func getBook(withISBN isbn: Int, completion: @escaping (Result<Book, Error>) -> Void) {
...
self.bookRepository.getBook(withISBN: isbn, completion: { [weak self] result in // second closure
switch result {
case .success(let book):
// saves new book if the instance is still alive
self?.books.append(book)
completion(.success(book))
case .failure(let erorr):
completion(.failure(error))
}
})
}

In the fix above we have added [weak self] to the closure. This stops the closure from retaining a strong reference–thus the use of the keyword weak. In simple the reference count to the CachedBookRepository is not increased. When the book search screen–which owns a strong reference to CachedBookRepository–is dismissed then the reference to CachedBookRepository instance falls to 0 and the instance is cleared from memory.

Conclusion

Closures and escaping closures provide a lot power and flexibility to the Swift language however there are risks associated when using them. Make sure you understand these before using them. Even experienced developers occasionally miss on retain cycles. You can find out if you’re suffering from any using Leaks instrument within Xcode.

Want to Connect?

For more on iOS development follow me on Twitter!

--

--

Anurag Ajwani

Senior iOS Engineer at Travelperk. 7+ years experience with iOS and Swift. Blogging my knowledge and experience.