Extensions in Swift allow you to extend an existing class, struct, enumeration, or protocol with new functionality. Whether it’s a custom type defined by you or a current type inside of a framework, extensions can create custom accessors and enhance the types you work with.
So-called retroactive modeling allows you to extend types for which you don’t have access to the original source code. This powerful feature of Swift enables you to create custom layers on top of external code. When and how to use them is an important skill to own as an app developer, so let’s dive in.
What is an extension?
Quite literally, an extension extends existing code. For example, you might have a Person
struct with a first and last name:
struct Person {
let firstName: String
let lastName: String
}
You can create an extension for this structure and add a fullName
property:
extension Person {
var fullName: String {
firstName + " " + lastName
}
}
In this case, we have access to the source code of the Person
structure, so we could also decide to define the fullName
computed property inside the struct itself:
struct Person {
let firstName: String
let lastName: String
var fullName: String {
firstName + " " + lastName
}
}
This makes more sense for this example as it keeps all the available properties in a single place.
Extending types without having access to the source code
When you’re unable to customize the implementation of a type due to lacking source code access, you can use extensions to create custom accessors. For example, we could create a Date
extension to add a days to the current value:
extension Date {
func addingDays(_ days: Int) -> Date {
Calendar.current.date(byAdding: .day, value: days, to: self)!
}
}
We’ve now extended the Date
type by adding a new method. We can use that method as follows:
Date().addingDays(7)
Even though we can’t access the source code of the Date
type, we have been able to extend it with custom functionality. This can be a great way to create discoverable code, since any extension method will be findable using autocompletion:
By making your custom code more discoverable, you allow others in your team to discover custom (often helpful) extensions. This works better than creating a custom type (E.g., DateDaysCalculator
) that everyone should be aware of.
Using extensions to create default protocol implementations
Protocol interfaces don’t allow you to write optional members, even though some methods might not be required for every instance. Secondly, you might want to provide default behavior and enable types to overwrite this if needed. We can use extensions to create this behavior.
For example, imagine having the following NameContaining
protocol:
protocol NameContaining {
var firstName: String { get }
var lastName: String { get }
var fullName: String { get }
}
We could make our Person
struct conform to this protocol without any changes:
struct Person: NameContaining {
let firstName: String
let lastName: String
var fullName: String {
firstName + " " + lastName
}
}
Now imagine we would introduce another NameContaining
type, but this time it’s a Pet
:
struct Pet: NameContaining {
let firstName: String
let lastName: String
var fullName: String {
firstName + " " + lastName
}
}
You can see we have to duplicate the computed fullName
property, even though its functionality matches the Person
‘s implementation. Using a protocol extension, we can remove boilerplate code while keeping the same functionality:
protocol NameContaining {
var firstName: String { get }
var lastName: String { get }
var fullName: String { get }
}
extension NameContaining {
/// This computed property provides a default implementation for the `NameContaining` protocol.
var fullName: String {
firstName + " " + lastName
}
}
struct Person: NameContaining {
let firstName: String
let lastName: String
}
struct Pet: NameContaining {
let firstName: String
let lastName: String
}
This technique is powerful and prevents you from writing many boilerplate code. In case you do want to provide a custom implementation, you can always decide to overwrite the default implementation:
struct Person: NameContaining {
let firstName: String
let lastName: String
var fullName: String {
let firstNameFirstChar = firstName.first!.uppercased()
return firstNameFirstChar + ". " + lastName
}
}
struct Pet: NameContaining {
let firstName: String
let lastName: String
}
let person = Person(firstName: "Antoine", lastName: "van der Lee")
let pet = Pet(firstName: "Bernie", lastName: "van der Lee")
/// Prints: "A. van der Lee" using the custom implementation of `fullName`
print(person.fullName)
/// Prints "Bernie van der Lee" using the default implementation of `fullName`
print(pet.fullName)
Conforming to a protocol using an extension
Extensions in Swift also allow you to add protocol conformance. This can be useful if you want to define protocol conformance outside the source file or in cases where you don’t have access to the source code.
For example, we could extend the CNContact
type of the ContactsUI
framework and make it conform to our NameContaining
protocol:
import ContactsUI
extension CNContact: NameContaining {
var firstName: String {
givenName
}
var lastName: String {
middleName + familyName
}
var fullName: String {
givenName + middleName + familyName
}
}
This now enables us to create instances that work with the NameContaining
protocol, without knowing whether they’re dealing with a Pet
, Person
or CNContact
:
import SwiftUI
struct NameView: View {
let nameContaining: NameContaining
var body: some View {
Text("This person's name is: \(nameContaining.fullName)")
}
}
Conclusion
Extensions in Swift are a powerful feature that allows you to write extensions to structs, classes, protocols, or enumerations. Whether you’ve access to the source code or not, extensions can extend any of those types. Using extensions in conjunction with protocols, you can write reusable code without too much boilerplate.
If you like to improve your Swift knowledge, even more, check out the Swift category page. Feel free to contact me or tweet to me on Twitter if you have any additional tips or feedback.
Thanks!