Extensions In Swift Explained

Written by Reinder de Vries on November 15 2018 in App Development

Extensions In Swift Explained

Extensions in Swift are super powerful, because they help you organize your code better. You use an extension to add new functionality to an existing class. And they’re especially useful if you don’t have access to the original code of that class!

In this article you’ll learn:

  • What extensions in Swift are, and why they’re useful
  • Common use cases for extensions in practical iOS development
  • How you can organize your code better with extensions
  • What extensions can’t do, and pitfalls to watch for

Ready? Let’s go.

  1. What’s An Extension?
  2. Separating And Grouping Code With Extensions
  3. Using Protocol Conformance
  4. Namespaced Constants And Nested Types
  5. Extensions, Computed Properties And Helpers
  6. Protocol Extensions
  7. Further Reading

What’s An Extension?

With an extension you can add new functionality to an existing Swift class, structure, enumeration or protocol type. You can literally add on new functions to an existing class, even if you don’t have access to the original source code of that class.

Let’s look at an example. Imagine that another developer has defined a class in their code, and you don’t have access to that code. Something like this:

class Airplane
{
    var altitude: Double = 0

    func setAltitude(feet: Double) {
        altitude = feet
    }
}

That other developer decided to measure the airplane’s altitude in feet, but you want to set it in meters. You can’t edit their code, and you don’t want to subclass Airplane, so you create a Swift extension instead.

Like this:

extension Airplane
{
    func setAltitude(meter: Double) {
        altitude = meter * 3.28084
    }
}

You can now use the setAltitude(meter:) function on an instance of class Airplane as an ordinary function, like this:

let boeing = Airplane()
boeing.setAltitude(meter: 12000)
print(boeing.altitude)
// Output: 39370.08

So, what’s really going on here? When your Swift code compiles, any extensions you’ve defined are added to their respective classes. The original class and its extensions are essentially merged into one. As a result, you can extend a class with new functionality, such as functions.

Here’s what extensions can do in Swift:

  • Add functions and computed properties
  • Provide new initializers, i.e. constructor functions
  • Define subscripts with the subscript() function
  • Define and use new nested types, i.e. add a subtype to a type
  • Make an existing type conform to a protocol, which is super useful
  • Add default implementations to protocols with protocol extensions

Extensions can’t change the fundamental structure of a class (or other type). They can only add functionality, not replace it. As such, you can’t add new properties to an existing type. Continuing with the previous example, you can’t do this:

extension Airplane
{
    var speed: Int = 0
}

Why not? Adding a new property to a class, with an extension, would fundamentally change the structure and needed memory of that class. And we already have a superb way to change the structure of a class: subclassing.

What’s so interesting about extensions is that you can use them to your advantage, by organizing your code better. Let’s take a look at these practical use cases for extensions:

  • Separating and grouping code
  • Using protocol conformance
  • Namespaced constants
  • Extensions, computed properties and helpers
  • Protocol extensions

Separating And Grouping Code With Extensions

A powerful way to use extensions is separating and grouping different parts of a class. You use a base class for the fundamental code of the class, such as its properties, and then add on functions by using extensions.

Let’s take a look at an example:

class Airplane
{
    var speed: Double = 0
    var altitude: Double = 0
    var bearing: Double = 0
}

Then, we define different extensions. First, one for flying the plane:

extension Airplane
{
    func changeAltitude(altitude: Double) {
        self.altitude = altitude
    }

    func changeBearing(degrees: Double) {
        self.bearing = degrees
    }
}

Then, we define functions for take-off and landing:

extension Airplane
{
    func takeOff() 
    {
        // Do take-off magic...
    }

    func land()
    {
        // Please stow hand luggage and move your seat to an upright position...
    }
}

Each extension can have its own Swift file. When you organize a class based on its functionality, and don’t put it all in one file, you can have greater control over where you put what code.

Consider for instance that you’re coding a view controller. The view controller has two main features. You can put each of those features in its own extension, and still use the view controller as one complete class.

The example with the Airplane is of course trivial, but you can imagine that breaking up components in extensions can help you organize a larger code base better. The key is to think about how to organize your code. An approach to start with, would be organizing your code by functionality.

Learn how to build iOS apps

Get started with iOS 12 and Swift 4

Sign up for our iOS development course Zero to App Store and learn how to build professional iOS 12 apps with Swift 4 and Xcode 10.

Using Protocol Conformance

You can use extensions to conform to a protocol. Like this:

class DetailViewController: UIViewController 
{
    // Do view controller magic...
}

extension DetailViewController: CLLocationManagerDelegate
{
    // Put all location code in here...
}

When a class conforms to multiple protocols, its class declaration can get messy quickly. And you end up with a huge class, which is generally a bad thing.

Just as with the previous example, you can break up and group a classes code by putting the functionality of each protocol into its own extension. The extension makes the base class adopt a protocol, and you add protocol-specific code to the extension.

Always keep in mind though that extensions shouldn’t be an excuse to skimp on proper app architecture. A huge class separated in 10 extensions is still a huge class, no matter how you slice it.

Namespaced Constants And Nested Types

You can’t add properties to an extension, but you can add static constants and subtypes to extensions. And that lets you do some Swift magic!

Here, consider this example:

extension Notification.Name {
    static let statusUpdated = Notification.Name("status_updated")
}

The above code extends the Notification.Name type with a static constant called statusUpdated. This class constant is available anywhere on the Notification.Name type, which lets you use it in conjunction with NotificationCenter. Like this:

NotificationCenter.default.post(name: .statusUpdated, object: nil)

The post(name:object:) function expects a value of type Notification.Name as its first parameter. You’ve defined the constant statusUpdated on Notification.Name, so Swift will infer its type, and you can use the shorter .statusUpdated syntax.

You can do a similar thing with subtypes. You can define a subtype in an extension, like this:

extension UserDefaults 
{
    struct Keys 
    {
        static let useSync  = "use_sync"
        static let lastSync = "last_sync"
    }
}

And you can use that as follows:

UserDefaults.default.set(true, forKey: UserDefaults.Keys.useSync)

This has two benefits:

  • The Keys subtype is only available on the UserDefaults type, which means that you won’t have an arbitrary struct defined at a global level in your code. This is similar to class-level namespacing in other programming languages – something that Swift cannot do.
  • The use of constants simply means that you’re less likely to make errors when typing "use_sync" for the UserDefaults key. You can use the same concept anywhere you use static keys.

Extensions, Computed Properties And Helpers

Swift is a super useful programming language, but sometimes it just doesn’t have what you need.

Consider for instance that prior to Swift 4.2, arrays had no simple shuffled() function to randomize arrays. It’s lunacy to subclass arrays just to be able to randomize them, so you resort to using a global helper function.

What if you could add that helper function directly to the Array type? Like this:

extension Array
{
    func shuffled() 
    {
        // Shuffle array items and return shuffled array
    }
}

The above code merely adds a shuffled() function to the Array type. As a result, that function is available on any array. Neat!

Here’s another helpful example:

class Circle {
    var radius: Double = 0
}

extension Circle
{
    var circumference:Double {
        return radius * .pi * 2
    }
}

The above code first declares a Circle class, and then adds a circumference computed property with an extension. When a computed property is accessed (see below), the code within the squiggly brackets is executed and a value is returned.

And calculate the circumference of a circle like this:

let circle = Circle()
circle.radius = 10
print(circle.circumference)
// Output: 62.83185307179586

Again, a trivial example, but imagine you don’t have access to the source code of the original Circle class, or that you want to organize its functionality better.

Protocol Extensions

The last approach we’ll discuss is using protocol extension. Unlike protocol conformance (see above), protocol extensions let you directly extend a protocol. What’s really mind-boggling is that you can provide default implementations of protocols by using extensions.

Consider this simple protocol:

protocol Edible {
    func eat()
}

The Edible protocol defines a function eat(), so any class that wants to conform to the protocol needs to implement the eat() function. Something like this:

class Fish: Edible 
{
    func eat() {
        print("**CHOMP** **CRUNCH** Eating the fish...")
    }
}

So far so good! With protocol extensions, you can now extend the protocol to include a default implementation. Like this:

extension Edible
{
    func eat() {
        print("Eating the thing...")
    }
}

Note that the above code extends the protocol type Edible, and not the Fish class. Because of that protocol extension, any class that adopts the Edible protocol can now use the default implementation of eat(). Like this:

class Apple: Edible 
{
    // Do nothing...
}

let apple = Apple()
apple.eat()
// Output: Eating the thing...

See how we’re calling the eat() function on apple, even though the Apple class does not provide an implementation? That’s because of the default implementation from the protocol extension. Awesome!

Learn how to build iOS apps

Get started with iOS 12 and Swift 4

Sign up for our iOS development course Zero to App Store and learn how to build professional iOS 12 apps with Swift 4 and Xcode 10.

Further Reading

Extensions increase the composability of your code, they let you add functionality to classes you can’t change, and help you organize your code better. And they’re downright awesome!

Want to learn more? Check out these resources:

Reinder de Vries

Reinder de Vries

Reinder de Vries is a professional iOS developer. He teaches app developers how to build their own apps at LearnAppMaking.com. Since 2009 he has developed a few dozen apps for iOS, worked for global brands and lead development at several startups. When he’s not coding, he enjoys strong espresso and traveling.