Learn about special opt-in extensions to get more control, and avoid namespace clashes across modules.
Extensions are a convenient technique to power up types in Swift to make our lives easier. But it can be a double-edged sword. If another developer isn’t careful, you may import a framework and run into an overload of unwanted extensions, or even worse, a clash where the owner of a framework has a same-named extension as one of yours or even another framework!
This risk is even higher once a framework extends a type that the framework doesn’t own—e.g., importing a framework that extends UIKit. The risk becomes higher because Swift doesn’t support namespaced extensions.
If we want to offer a framework—or even full-blown SDK—we can be a good housekeeper and offer opt-in extensions, where the implementer of your framework won’t get extensions unless they explicitly ask for them. Let’s see how this works.
We are going to cover extensions in combination with subclasses to showcase a more granular control of extensions. Let’s use UIView
as an example since it’s commonly subclassed when working with iOS.
Imagine that we want to log a whole tree—pun intended—of a UIView
and its subviews. We can offer a special printTree()
method inside a UIView
extension, but this has a problem which we’ll cover shortly.
extension UIView: Loggable {
func printTree() {
print(self)
// We recursively call printTree for each subview that we find.
subviews.forEach { $0.printTree() }
}
}
Now if we have a UIView
instance, we can call printTree()
and we’ll see its view and all of its subviews printed in the console log.
The problem is that all UIView
instances have this method available; This goes for both UIView
instances as well as any of its subclasses.
Before we take this approach and leave it, really think about it. UIView
often gets subclassed, and the way we wrote our extension allows all view instances to log itself. Another developer gets the printTree()
method on their instances, whether she likes it or not.
Even worse, if we were to make this extension public inside a framework, the implementer of this framework gets this method in their project and potentially cause a naming clash. It’s the Swift equivalent of a framework deciding to swizzle classes without your consent.
Let’s be a well-disciplined developer and offer an opt-in extension; This allows another developer to decide when and if they want our extension.
First, we’ll define a Loggable
protocol that defines the printTree()
method; this means that we throw away the previous UIView
extension.
protocol Loggable {
func printTree()
}
Instead of extending UIView
, we extend the Loggable
protocol and constrain it to UIView
. This way, the extension becomes available on some UIView
instances, but not all of them.
In this extension, we’ll offer a free printTree()
method implementation once a UIView
(sub)class constrains itself to the Loggable
protocol.
extension Loggable where Self: UIView {
func printTree() {
printChildren(view: self) // Calls recursive helper method
}
/// Recursively print children
func printChildren(view: UIView) {
view.subviews.forEach { view in
print(view)
printChildren(view: view)
}
}
}
We can’t recursively call printTree
because not all subviews might conform to Loggable
. Instead, we use a helper method called printChildren
which we can apply to all subviews. Whereas printTree
is only available on Loggable
subviews.
We’ve created an extension with opt-in behavior, now if a developer manually conforms a UIView
subclass to Loggable
, the printTree()
method becomes available.
Notice how a regular UIView
doesn’t get the printTree()
method, but if a custom UIView
adheres to Loggable
, it will get the printTree()
method for free.
let view = UIView((frame: CGRect.zero)
view.printTree() // not available
// Creating a custom UIView subclass
final class CustomView: UIView, Loggable {
}
let customView = CustomView(frame: CGRect.zero)
customView.printTree() // The printTree method is available on CustomView
The downside is that a regular UIView
won’t get this method. But that’s easily solved by making UIView
explicitly implement the Loggable
protocol.
extension UIView: Loggable {}
This way, giving UIView
the printTree()
extension now happens on our terms, and not by default.
Become a Swift pro
Updated for Swift 5!
Check out Swift in Depth and master tough concepts; Such as Generics, Functional Programming concepts, and Protocol-Oriented Programming.
Once you start working with larger projects comprising out of multiple frameworks, you may find yourself running into extensions-overload, where developers get creative by adding many extensions on many types.
Offering extension via an opt-in approach gives your fellow developers more control when and if they want extensions. It makes it more explicit when added functionality is used, which is especially helpful if you’re offering a framework or SDK.
We used UIView
as an example, but you can imagine that more scenarios benefit from opt-in extensions; Such as having analytics protocols implemented by viewcontrollers or nsobjects, or clever constraints extensions added to viewcontrollers or views.
Written by
Tjeerd in 't Veen has a background in product development inside startups, agencies, and enterprises. His roles included being a staff engineer at Twitter 1.0 and iOS Tech Lead at ING Bank.