Swift Closures: Functions with a Memory
A closure is a function carrying luggage: it captures surrounding variables and remembers them later. Use them in SwiftUI actions, async completions, and array transforms. Beware a strong reference cycle from capturing self without a capture list.
WHY IT EXISTS: Functions sometimes need to run long after the context that created them has disappeared. If a function could only use its own parameters and global variables, every asynchronous callback would need cumbersome manual state passing. Closures solve this by letting a function bundle up the local variables it needs and take them along for later execution.
THE MENTAL MODEL: Think of a closure as a function with a backpack. Inside the backpack are copies or references to variables from the place where the function was created. When the function is called later, maybe inside a network completion handler or a SwiftUI animation, it opens that backpack and accesses those captured values as if the original scope still existed.
HOW IT WORKS: In Swift, a closure is a self-contained block of functionality that can be passed around and used in your code. When a closure references a variable or constant from its surrounding scope, Swift captures it. For value types like Int or String, Swift captures a copy. For reference types like classes, Swift captures a reference to the instance, which means the closure shares ownership unless you specify otherwise. You control capture behavior with a capture list written in square brackets before the parameter list, such as [weak self] or [unowned self].
WHEN TO USE IT: Use closures for short-lived operations that need context without the boilerplate of a dedicated class or function. Common examples include completion handlers for URLSession data tasks, SwiftUI view modifiers, trailing closure syntax with array operations like sorted or filter, and escaping closures that outlive the function they are passed to.
WHEN NOT TO USE IT: Do not use a closure when the logic is complex enough to need multiple methods or stored properties; a dedicated struct or class is clearer. Avoid overly long trailing closures that force readers to scroll to understand what the trailing brace belongs to. And never ignore capture lists when self is referenced inside an escaping closure, because the default strong capture will keep the object alive and create a memory leak.
ONE CANONICAL EXAMPLE: A network manager fetches user data and returns it in a completion handler. Inside the view controller, you write a data task with a trailing closure that includes the capture list [weak self], then unwraps self, and calls updateUI. Without [weak self], the closure strongly captures the view controller, the network task strongly captures the closure, and the view controller cannot deallocate until the network call finishes or the app terminates. Adding [weak self] breaks that cycle and lets memory management behave correctly.
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.