I make ample use of self-executing closures in a range of scenarios. In this episode, I show you several patterns in which self-executing closures come in useful and can improve the code you write.
What Is It?
Before I show you the different uses of self-executing closures, I want to make sure you understand what a self-executing closure is. Let me show you an easy to understand example in a playground. We define a variable, myVar
, and assign an empty closure to the variable. This should look familiar.
import Foundation
var myVar = {
}
In the body of the closure, we create and return a string literal. Can you guess what the type of myVar
is? Press Option and click myVar
to find out. The type of myVar
is a closure that accepts no arguments and returns a string.
As the name implies, a self-executing closure is a closure that is immediately executed. Some developers use the term immediately-executing closure for that reason. Let's append a pair of parentheses to the closure to execute it.
import Foundation
var myVar = {
return "this is a string"
}()
Press Option and click myVar
. The type of myVar
is no longer a closure, it is now of type String
. By appending a pair of parentheses to the closure, the closure is executed and the return value of the closure is assigned to myVar
.
Lazy Properties
Assigning an initial value to a lazy property is probably the most common use of self-executing closures. This pattern is convenient if the initialization of an object is computationally expensive, requires several lines of code, or needs access to self
, the owner of the object. Take a look at this example.
private lazy var tableView: UITableView = {
// Initialize Table View
let tableView = UITableView(frame: .zero)
// Configure Table View
tableView.delegate = self
tableView.dataSource = self
tableView.translatesAutoresizingMaskIntoConstraints = false
return tableView
}()
Because the property declaration is prefixed with the lazy
keyword, we can access self
in the self-executing closure. This is possible because the self-executing closure is executed after the initialization of self
, the owner of the object. In this example, self
refers to the view controller that owns the table view.
What I like about this pattern is that the initialization and configuration of the table view are encapsulated by the self-executing closure. There's no need to set up the table view in viewDidLoad()
or another method. This makes your code readable and easier to understand.
Constant Properties
It's a common misconception that self-executing closures can only be used with variable properties. It's perfectly fine to use self-executing closures with constant properties. The subtle benefit is that the property is declared as a constant instead of a variable. Lazy properties always need to be declared as variable properties because the value is set after the initialization of the owner of the object.
The drawback of a constant property is that you can't access self
, the owner of the object, in the self-executing closure. Why is that? When used in combination with a constant property, the self-executing closure is executed during the initialization of the owner of the object instead of after the initialization.
private let titleLabel: UILabel = {
// Initialize Title Label
let titleLabel = UILabel(frame: .zero)
// Configure Title Label
titleLabel.numberOfLines = 0
titleLabel.translatesAutoresizingMaskIntoConstraints = false
return titleLabel
}()
Encapsulating Code
Self-executing closures can be used in a range of scenarios, not only to initialize properties. Take a look at this example.
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
// Helpers
var tableViewIsHidden = true
if viewState == .hasData {
if userState == .signedIn {
tableViewIsHidden = false
}
}
// Configure Table View
tableView.isHidden = tableViewIsHidden
}
We define a variable, tableViewIsHidden
, and assign a default value to the variable, true
. The next few lines update the value of the variable by evaluating the state of the view and the user. Using a variable is a common pattern if you don't know what the value of the variable should be the moment you define it.
You probably know that you should use constants over variables whenever possible. If the value of tableViewIsHidden
doesn't change, then there's no need to declare it as a variable. Let's see if that is possible.
There's nothing inherently wrong with the current implementation, but I don't like the idea of using a variable in this scenario. We can replace the variable with a constant by using a self-executing closure. The code that modifies the variable is moved to the body of the closure and the return value of the closure is assigned to the variable. Because the variable is no longer modified, we can replace it with a constant.
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
// Helpers
let tableViewIsHidden: Bool = {
if viewState == .hasData {
if userState == .signedIn {
return false
}
}
return true
}()
// Configure Table View
tableView.isHidden = tableViewIsHidden
}
Because we use a self-executing closure, we can simplify the code we write by exiting the self-executing closure early using if
statements. As an added bonus, the code becomes easier to read and understand.
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
// Helpers
let tableViewIsHidden: Bool = {
if viewState == .loading { return true }
if userState == .anonymous { return true }
return false
}()
// Configure Table View
tableView.isHidden = tableViewIsHidden
}
I like this pattern for several reasons. The obvious benefit is that we replace a variable with a constant. Anyone reading the code implicitly understands that the value of tableViewIsHidden
won't change. Another, more subtle, benefit is readability. The body of the closure contains the code that defines the value of the constant. Because we use a constant, we know that its value is defined in the self-executing closure and it won't be modified elsewhere.
Threading and Access Control
Lazy properties are convenient and they are easy to use, but it is important that you know and understand how they can impact the code you write. Lazy properties are not thread safe and for that reason some developers argue that you should only use them when they are needed, not when it's convenient to use them. I can appreciate this argument, but I don't agree. If you understand the risk of using lazy properties, then it's up to you to decide whether it's appropriate to use them.
It is perfectly fine to declare the views of a view controller as lazy properties because you know that these properties should only be accessed from the main thread. This isn't true if you're creating an object that is accessed by a range of different objects from different threads. In that scenario, you either implement a threading strategy that prevents threading issues or you don't declare the property as lazy. I recommend keeping it simple and avoid lazy properties in the latter scenario. Debugging threading issues isn't something most developers do for fun. I always try to keep code as simple as possible if I know that threading issues are a potential risk.
Keep in mind that lazy properties need to be declared as variables and that implies that they can be modified. That is a considerable drawback in my opinion. You can work around this by correctly applying access control modifiers. If no other objects need access to a lazy property, then apply the private
or fileprivate
modifier to lock down access.
What's Next?
While self-executing closures are not unique to Swift, they can add clarity to the code you write. When and why you use self-executing closures is a personal choice. Don't use self-executing closures because that is what you should do. If you don't feel they fit your coding style, then only use them when necessary. For me, writing code is very similar to writing a book or painting a picture, I tweak and mold the code until it feels right. It isn't important if that takes two, three, or ten passes. Commit your code once you're happy with the result.