Swift and SwiftUI for Web Developers Closures
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 I really 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.
Need a Break?
I built Pausly to help people like us step away from the screen for just a few minutes and move in ways that refresh both body and mind. Whether you’re coding, designing, or writing, a quick break can make all the difference.
Give it a try