Best examples of practical examples of using closures in Swift
Real examples of practical examples of using closures in Swift
Let’s start with where closures show up constantly: sorting, filtering, and mapping collections. These are some of the best examples of using closures in Swift because they’re short, readable, and you’ll use them every day.
struct User {
let id: Int
let name: String
let lastLogin: Date
}
var users: [User] = loadUsersFromDisk()
// Sort by most recent login using a closure
let sortedByLastLogin = users.sorted { lhs, rhs in
lhs.lastLogin > rhs.lastLogin
}
// Filter active users based on a closure predicate
let activeUsers = users.filter { user in
user.lastLogin > Date().addingTimeInterval(-7 * 24 * 60 * 60) // last 7 days
}
// Map to just names using a closure
let userNames = users.map { $0.name }
These examples of practical examples of using closures in Swift show three patterns you’ll see constantly:
- Passing a closure as a comparator (
sorted) - Passing a closure as a predicate (
filter) - Passing a closure as a transformer (
map)
They’re not just syntactic sugar. These closure-based APIs encourage you to write small, focused bits of logic and pass them around like values.
Networking and async work: examples of completion closures in Swift
Networking is where many people first meet completion handlers. A classic example of practical examples of using closures in Swift is a function that fetches data and calls a closure when it’s done.
enum NetworkError: Error {
case invalidURL
case requestFailed
}
func fetchUsers(completion: @escaping (Result<[User], NetworkError>) -> Void) {
guard let url = URL(string: "https://example.com/api/users") else {
completion(.failure(.invalidURL))
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
if let _ = error {
completion(.failure(.requestFailed))
return
}
guard let data = data else {
completion(.failure(.requestFailed))
return
}
do {
let users = try JSONDecoder().decode([User].self, from: data)
completion(.success(users))
} catch {
completion(.failure(.requestFailed))
}
}.resume()
}
This is a very typical example of using closures in Swift networking code:
- The
completionclosure captures the result of the async work. - The function returns immediately, but the closure runs later.
@escapingtells Swift that the closure outlives the function call.
With Swift’s async/await (stabilized in Swift 5.5 and widely used by 2024), you often bridge older closure-based APIs into async functions.
func fetchUsersAsync() async throws -> [User] {
try await withCheckedThrowingContinuation { continuation in
fetchUsers { result in
switch result {
case .success(let users):
continuation.resume(returning: users)
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
This is one of the best examples of practical examples of using closures in Swift in 2024-era code: closures act as the glue between legacy completion handlers and modern async/await.
UI callbacks: examples include button actions and animations
UIKit and SwiftUI are full of closure-based APIs. These UI callbacks are some of the clearest real examples of practical examples of using closures in Swift.
UIButton actions with closures (UIKit)
Instead of using target–action with selectors, many teams wrap button taps in closures:
final class ClosureButton: UIButton {
private var onTap: (() -> Void)?
func setOnTap(_ handler: @escaping () -> Void) {
onTap = handler
addTarget(self, action: #selector(handleTap), for: .touchUpInside)
}
@objc private func handleTap() {
onTap?()
}
}
// Usage
let button = ClosureButton(type: .system)
button.setOnTap { [weak self] in
self?.showDetailScreen()
}
Here the closure becomes the action. You capture self weakly to avoid retain cycles (more on that in a moment).
UIView animations
Apple’s animation APIs are textbook examples of practical examples of using closures in Swift:
UIView.animate(withDuration: 0.3, animations: {
view.alpha = 0
}, completion: { finished in
if finished {
view.removeFromSuperview()
}
})
Two closures:
- The
animationsclosure describes what changes over time. - The
completionclosure runs when the animation ends.
This pattern—“describe behavior now, run it later”—is exactly why closures are so widely used in UI frameworks.
SwiftUI: closures everywhere (view builders and actions)
SwiftUI is basically a masterclass in examples of practical examples of using closures in Swift. View builders, event handlers, and modifiers all lean on closures.
struct UserListView: View {
let users: [User]
let onSelectUser: (User) -> Void
var body: some View {
List(users, id: \ .id) { user in
Button(user.name) {
onSelectUser(user)
}
}
}
}
struct ContentView: View {
@State private var selectedUser: User?
var body: some View {
UserListView(users: loadUsersFromDisk()) { user in
selectedUser = user
}
}
}
Here are a few interesting closure-related details:
Listtakes a closure that builds each row.Buttontakes a closure for its action.UserListViewitself exposes a closure (onSelectUser) so parents can inject behavior.
This is a real example of how closures enable inversion of control: the child view doesn’t know what happens when a user is selected; it just calls the closure.
Capturing values and avoiding retain cycles
Closures capture values from their surrounding scope. That’s powerful, but it’s also how you accidentally create memory leaks.
Consider this example of practical examples of using closures in Swift where a view model owns a service that has a callback:
final class UserViewModel {
private let service: UserService
init(service: UserService) {
self.service = service
}
func startListening() {
service.onUserUpdate = { [weak self] users in
self?.handleUpdate(users)
}
}
private func handleUpdate(_ users: [User]) {
// update state
}
}
final class UserService {
var onUserUpdate: (([User]) -> Void)?
}
Without [weak self], the closure would capture self strongly, and if UserService also holds UserViewModel strongly, you’d get a retain cycle.
This pattern—storing a closure property and using a capture list—is one of the most important real examples of how closures interact with ARC. Apple’s Swift Programming Language Guide has an in-depth section on closure capture semantics.
Background work and GCD: closures as work units
Grand Central Dispatch (GCD) is another area where the best examples of using closures in Swift appear. Dispatch queues use closures as the unit of work.
let backgroundQueue = DispatchQueue(label: "com.example.background", qos: .userInitiated)
func processLargeFile(url: URL, completion: @escaping (Result<Data, Error>) -> Void) {
backgroundQueue.async {
do {
let data = try Data(contentsOf: url)
DispatchQueue.main.async {
completion(.success(data))
}
} catch {
DispatchQueue.main.async {
completion(.failure(error))
}
}
}
}
Here you see two layers of closure usage:
- A closure passed to
backgroundQueue.asyncto run work off the main thread. - A closure passed as
completionto notify the caller.
This is a classic example of practical examples of using closures in Swift to separate where work runs from what the work actually is.
Functional pipelines: transforming data with chained closures
Swift’s standard library encourages a functional style. Chaining map, filter, and reduce is one of the cleanest examples of practical examples of using closures in Swift for data processing.
struct Order {
let id: Int
let total: Double
let isRefunded: Bool
}
let orders: [Order] = loadOrders()
let totalRevenue = orders
.filter { !$0.isRefunded } // closure as predicate
.map { $0.total } // closure as transformer
.reduce(0.0, +) // closure as accumulator
You can contrast this with a more imperative version using loops and if statements. The closure-based pipeline is shorter, often easier to test, and reads like a description of the business rule.
For more on this style of thinking, many university courses (for example, functional programming courses at MIT) discuss higher-order functions, which is exactly what Swift’s collection methods are.
Escaping vs non-escaping closures in modern Swift
By 2024, the Swift compiler is very good at inferring when a closure is escaping. Still, understanding the difference is important, and examples of practical examples of using closures in Swift make it easier to see.
A non-escaping closure is used only during the function’s execution:
func performTwice(_ operation: () -> Void) {
operation()
operation()
}
performTwice {
print("Hello")
}
Here, operation doesn’t outlive performTwice, so no @escaping is needed.
Compare that to this example of an escaping closure:
var storedHandlers: [() -> Void] = []
func storeHandler(_ handler: @escaping () -> Void) {
storedHandlers.append(handler)
}
storeHandler {
print("I’ll run later")
}
The closure is stored for later use, so it must be marked @escaping. Many of the earlier examples of practical examples of using closures in Swift—networking, GCD, callbacks—are escaping closures by nature.
Apple’s official docs on @escaping are worth reading if you want the language-lawyer details.
Timers and scheduled work: closures as delayed actions
Timers are another real example of practical examples of using closures in Swift. Modern APIs let you schedule repeating work with a closure instead of selectors.
final class HeartbeatLogger {
private var timer: Timer?
func start() {
timer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { [weak self] _ in
self?.logHeartbeat()
}
}
func stop() {
timer?.invalidate()
timer = nil
}
private func logHeartbeat() {
print("Heartbeat at \(Date())")
}
}
This pattern shows up in analytics, health tracking, and background monitoring. While you’d use more specialized frameworks for medical-grade monitoring (see resources from organizations like the NIH for serious health applications), the closure-based timer pattern is the same.
Higher-order utilities: building APIs that accept closures
So far we’ve mostly passed closures into system APIs. Let’s flip it around and build our own small utility that takes closures.
func measureExecutionTime(label: String, _ block: () -> Void) {
let start = CFAbsoluteTimeGetCurrent()
block()
let end = CFAbsoluteTimeGetCurrent()
print("[\(label)] took \(end - start) seconds")
}
measureExecutionTime(label: "Heavy work") {
// some expensive task
for _ in 0..<1_000_000 {
_ = UUID().uuidString
}
}
This is a compact example of practical examples of using closures in Swift to build reusable utilities:
- The
measureExecutionTimefunction doesn’t care what the work is. - The caller injects behavior via a closure.
- You can reuse this utility anywhere in your codebase.
This pattern scales all the way up to full-blown frameworks: pass behavior in, and the framework controls when and how it runs.
FAQ: short answers about real closure usage
Q: What are some real-world examples of using closures in Swift apps?
Examples include networking completion handlers, SwiftUI button actions, collection transformations (map, filter, reduce), GCD background tasks, timers, and view model callbacks that notify views of state changes.
Q: Can you give an example of avoiding retain cycles with closures?
Yes. Anytime a long-lived object stores a closure that references self, you typically write [weak self] in the capture list and then use self? inside the closure. The UserViewModel and HeartbeatLogger examples above both show this pattern.
Q: Are closures slower than regular functions in Swift?
In everyday app code, the difference is usually negligible. Swift’s optimizer is quite good at inlining small closures, especially non-escaping ones. For performance-sensitive code, you can measure it using a utility like measureExecutionTime or Instruments.
Q: How do closures relate to async/await in modern Swift?
Many older APIs use completion-handler closures. In Swift 5.5+ you can bridge those into async functions using withCheckedContinuation or withCheckedThrowingContinuation, as shown in the fetchUsersAsync example. Under the hood, async/await is still coordinating work in a closure-like way.
Q: When should I use a closure instead of a protocol?
Closures shine when you just need to pass behavior—"do this when X happens"—without defining a full type. Protocols are better when you need a named capability with multiple requirements or when you’ll have several conforming types. Many modern Swift APIs favor closures for simple callbacks and keep protocols for more structured abstractions.
Related Topics
Practical examples of simple calculator app examples in Swift
Practical examples of Swift async/await examples for asynchronous programming
Best examples of practical examples of using closures in Swift
Practical examples of UITableView implementation in Swift for modern iOS apps
Practical examples of timer examples with Grand Central Dispatch in Swift
The best examples of error handling in Swift: 3 practical examples you’ll actually use
Explore More Swift Code Snippets
Discover more examples and insights in this category.
View All Swift Code Snippets