I love that access control is an integral component of the Swift programming language. Access control lets you carefully control what properties and methods an entity exposes. Rigorously applying access control has significant benefits. You can even improve performance through access control.
Even though we agree that access control is great, outlets seem to fall in a different category. Most developers seem to overlook outlets when it comes to access control and don't question the code Xcode generates for them.
An entity without an explicit access level automatically receives the internal
access level. A property or method with an internal
access level can be accessed by other objects within the same module. Should that be the default?
If you create an outlet by pressing Control and dragging from a user interface element in Interface Builder to the corresponding source file, Xcode creates an outlet that doesn't explicitly specify an access level. In other words, it defaults to internal
.
import UIKit
class ViewController: UIViewController {
// MARK: - Properties
@IBOutlet weak var label: UILabel!
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
}
}
Defaulting to Private Outlets
A few weeks ago, a colleague pointed out that it is—or should be—a best practice to declare outlets privately by default. I never considered the value of declaring outlets as private
, but it immediately made sense. There are several benefits.
import UIKit
class ViewController: UIViewController {
// MARK: - Properties
@IBOutlet private weak var label: UILabel!
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
}
}
First, outlets are no different than other properties or methods. This means that you should keep them private if the entity shouldn't expose them to other objects.
Second, you prevent introducing subtle code smells. Let me explain what I mean by that. A view controller is responsible for managing a view and its subviews. That is the primary—and only—responsibility of a view controller. Right?
It is possible that other objects also want to have a say in how the view hierarchy of the view controller is managed. Take view controller containment as an example. A container view controller wants to control some parts of the view hierarchy of one of its child view controllers. This is possible, but that is something I consider a code smell.
If you declare outlets privately by default, the compiler throws an error if another object attempts to access an outlet of a view controller. This is very convenient because the compiler does the heavy lifting for you.
Use a Snippet
I rarely create outlets by dragging from Interface Builder to a source file. I use a snippet to define outlets. This is more convenient because, as of a few weeks ago, it automatically prepends the private
keyword to the variable declaration.
In Summary
The message is simple, declare entities privately by default. By adopting this practice, you only declare entities as internal
—or open
/public
—when you have a good reason for doing so. Outlets are no different. Default to private
.