Logo for tanaschita.com

Using defer in Swift to manage state cleanup

Learn how to use defer to handle cleanup logic in async functions.

21 Jul 2025 · 3 min read

When working with async code, we often need to manage UI state before and after an operation. A common example is showing and hiding a loading indicator.

In Swift, we can use the defer statement to ensure that cleanup code runs when the current scope exits, regardless of how we exit, successfully or due to an error.

Let's look at a typical use case.

Sponsorship logo
Architecture & Design Patterns for iOS
This book is a practical guide to essential architectural principles and design patterns for iOS development. It covers strategies for building maintainable apps with Swift and SwiftUI, including dependency injection, navigation, common patterns, and modularization.
LEARN MORE
func fetch() async {
isLoading = true
defer { isLoading = false }
do {
articles = try await service.fetchArticles()
} catch {
self.error = error.localizedDescription
}
}

In this example, we want to show a loading indicator while fetching data and hide it afterward. The defer block ensures that isLoading = false is always called when the function completes, whether it finishes normally or throws an error.

Without defer, we would achieve the same result in different ways. For example by setting isLoading = false in multiple places:

func fetch() async {
isLoading = true
do {
articles = try await service.fetchArticles()
isLoading = false
} catch {
self.error = error.localizedDescription
isLoading = false
}
}

Or by placing it once at the bottom of the function:

func fetch() async {
isLoading = true
do {
articles = try await service.fetchArticles()
} catch {
self.error = error.localizedDescription
}
isLoading = false
}

Both approaches work, especially in small functions like this. But as a function grows, it's easy to introduce subtle bugs. For example, if we add a new return somewhere, or if the task is cancelled during an await, we could accidentally skip resetting the state.

That's where defer helps. It guarantees that the block will run when the surrounding scope exits, in all of the following cases:

  • The function runs normally to the end
  • An error is thrown
  • There's an early return
  • The task is cancelled during an await call

By centralizing cleanup logic with defer in one place, we reduce the chance of mistakes and make our code more robust, especially in async or error-prone contexts.

Sponsorship logo
Preparing for a technical iOS job interview
Preparing for a technical iOS Job Interview with over 300 questions & answers. Covering Swift & Objective-C, SwiftUI & UIKit, Combine, HTTP Networking, iOS File System, Core Data, Concurrency with async/await, Security, Automated Testing, Dependency Management, AI & Machine Learning and more.
LEARN MORE
Sponsorship logo
Architecture & Design Patterns for iOS
This book is a practical guide to essential architectural principles and design patterns for iOS development. It covers strategies for building maintainable apps with Swift and SwiftUI, including dependency injection, navigation, common patterns, and modularization.
LEARN MORE
Sponsorship logo
Become a sponsor of tanaschita.com
By publishing an article on different iOS topics every week, tanaschita.com is constantly growing in the developer community and may provide a great audience for you as a sponsor.
CLICK TO LEARN MORE

Newsletter

Image of a reading marmot
Subscribe

Like to support my work?

Say hi

Related tags

Articles with related topics

swiftui

swift

concurrency

async/await

ios

Building responsive and reliable search in SwiftUI

Learn how to debounce search input, cancel obsolete requests and prevent stale results using Swift concurrency.

14 Sep 2026 · 6 min read

Latest articles and tips

© 2026 tanaschita.com

Privacy policy

Impressum