on
Swift - Leveraging the power of first-class functions
Functions, in Swift, are first-class citizens that can be passed as arguments, stored in properties or returned from functions.
When dealing with Swift built-in functions such as map
and forEach
, one often ends up using the trailing closure syntax to provide with the required behavior.
let stackView = UIStackView()
let views = [UIView(), UIView(), UIView()]
views.forEach { view in
stackView.addArrangedSubview(view)
}
Or, using the $ syntax:
views.forEach {
stackView.addArrangedSubview($0)
}
While the above implementation is quite clear, let’s see how we can leverage the power of first-class functions and improve code readability.
— Functions
First, let’s take a closer look at the forEach
expected function argument when looping over our views.
(body: (UIView) throws -> Void)
It expects a function that takes a UIView
as as argument and returns Void
.
Now, let’s look at the addArrangedSubview
function from the Swift documentation.
func addArrangedSubview(_ view: UIView) -> Void
Interestingly enough, the addArrangedSubview
method gives us exactly what we need to meet the forEach
argument expectation giving us the ability to directly pass the addArrangedSubview
into the forEach
function.
views.forEach(stackView.addArrangedSubview(_:))
The wildcard function argument is not necessary since the compiler can figure on its own what method it refers to.
views.forEach(stackView.addArrangedSubview)
It provides with a shorter syntax while enhancing readability on a code that could almost be read as plain English text.
Let’s wrap it up by removing our subviews from our UIStackView
.
views.forEach(stackView.removeArrangedSubview)
— Initializers
Since initializers
are also functions that takes a certain amount of arguments and returns the object they’re defined in, it becomes quite convenient to use such declarative syntax to write more legible code.
let imageStrNames = ["flower", "car", "boat"]
imageStrNames
.map(UIImage.init(named:))
.map(UIImageView.init)
.forEach(stackView.addArrangedSubview)
Notice how in the first map
, we explicitly specify the argument label to give the compiler a hint on which init
method we want to use (since a UIImage
has several initializers, it could otherwise lead to misbehaviors).
struct Student {
let name: String
}
let studentNames = ["John", "Bob", "Peter"]
let students = studentNames.map(Student.init(name:))
Again, the compiler is smart enough to use the proper initializer so the explicit init argument name
is not required.
let students = studentNames.map(Student.init)