Swift for Web Devs - Part 2 - Closures

Friday, October 4th, 2024 #dev

Swift for Web Devs - Part 2 - Closures

This is a post in a series about Swift and SwiftUI for web developers, aimed at highlighting the beautiful aspects of the language. To learn more about Swift, refer to the official documentation.


In the first part of this series, we explored the similarities between TypeScript and Swift and argued that Swift should feel quite familiar to web developers.

In this installment, I want to spotlight a particular feature of Swift that I found astounding when learning Swift for the first time: Closures!

Closures in Swift

Of course, closures are not a new concept in Swift, and as a TypeScript developer, you’re likely well acquainted with them.

Here’s how a typical closure looks in TypeScript:

function activeUserFilter(user: User) { return user.isActive }

And in Swift, it might look like this:

func activeUserFilter(user: User) -> Bool { user.isActive }

As mentioned in the previous post, Swift allows a function with just one expression in the body to omit the return keyword.

These two closures appear quite similar, so why am I so excited about them? It's the many simple niceties Swift introduces that make both reading and writing code a pleasure.

Accepting Closures

Writing a function that accepts a closure is also very similar to TypeScript:

func filterUsers(filterFunc: (User) -> Bool) -> [User] {
    let users] = getUsers()
    return users.filter(filterFunc)
}
// Now, the previously declared `activeUserFilter` can
// be passed into the function:
let newUsers = filterUsers(filterFunc: activeUserFilter)

(Dont’t worry about the filterFunc: syntax now — it’s not very Swift-Like. We’ll address that in a future post about argument labels.)

At this point, nothing seems particularly groundbreaking.

Creating Closures

Instead of creating a named function as we did before, Swift provides a briefer option: Closure Expression Syntax. This can be likened to an anonymous function in JavaScript.

Rather than defining the function upfront, it can be defined inline as an expression:

let newUsers = filterUsers(filterFunc: { user: User in
  user.isActive
})

A closure expression in Swift is encapsulated by curly braces, beginning with an argument list, followed by the in keyword, which demarcates the body of the closure.

This is analogous to the following in TypeScript:

const newUsers = filterUsers((user: User) => user.isActive)

Inferred Argument Types

Swift's type inference system is robust, like TypeScript's. Given that the filterUsers function already defines the function signature, it's unnecessary to specify the type of the user parameter:

let newUsers = filterUsers(filterFunc: { user in user.isActive })

That looks cleaner, but there’s more.

Shorthand Argument Names

Swift provides shorthand names for arguments ($0 for the first argument, $1 for the second, and so on), removing the need to name each argument explicitly.

This simplifies the previous example further:

let newUsers = filterUsers(filterFunc: { $0.isActive })

This level of succinctness is something I've always wanted in JavaScript, making it a lot faster to write anonymous functions.

Passing Closures

What really excites me, and what I enjoy each time I use Swift, is the concept of trailing closures!

If the last argument to a function is a closure, you can pass it as a trailing closure like this:

let newUsers = filterUsers() { $0.isActive }

Swift understands that the trailing closure needs to be passed as the filterFunc argument.

And, if the function doesn’t accept (or require) additional arguments, you can even omit the parentheses:

let newUsers = filterUsers { $0.isActive }

This syntax is both elegant and enjoyable to read.

Imagine you have a list of names that you wish to sort alphabetically in reverse order. With Swift, all you need is:

let reversedNames = names.sorted { $0 > $1 }

Conclusion

That’s it for this part. Stay tuned for the next post, where we’ll go over named argument labels among other things.

If you enjoyed this post, subscribe to this blog in the RSS reader of your choice.