How to Create an Abstract Class in Swift

The title of this tutorial is a bit misleading because the Swift programming language doesn't support abstract classes. Fortunately, there are workarounds. In this tutorial, we take a look at two alternatives to the abstract class pattern.

Abstract Classes

How It Works

The first workaround attempts to mimic the abstract class pattern. An example best illustrates this solution.

class Animal {
    func sound() {}
}

class Cat: Animal {
    override func sound() {
        print("miauw")
    }
}

class Dog: Animal {
    override func sound() {
        print("woof")
    }
}

This is a good start. The override keyword is required since we're overriding a method implemented by the superclass. The Animal class isn't abstract, though. How do we force Cat and Dog to implement the sound() method. This is a possible solution.

class Animal {
    func sound() {
        fatalError("Subclasses need to implement the `sound()` method.")
    }
}

If the sound() method of the Animal class is invoked, a fatal error is thrown. How does this work? And how does that mimic the abstract class pattern? Let's look at another example.

class Cat: Animal {
    override func sound() {
        print("miauw")
    }
}

class Dog: Animal {

}

let kitty = Cat()
kitty.sound()

let bobby = Dog()
bobby.sound()

Swift uses dynamic dispatch to determine which sound()method to invoke. What does that mean? In the updated implementation of the Dog class, the sound() method is remove. The subclass does not override the sound() method of its parent class, Animal. This is a red flag for the abstract class pattern.

If we instantiate an instance of the Cat class and invoke the sound() method on kitty, the runtime inspects the class hierarchy to determine which sound() method to invoke. If the Cat class has an implementation of the sound() method, then that implementation is used since it overrides the implementation of the superclass.

The same applies to the Dog class. The difference is that the Dog class doesn't implement the sound() method. This means the sound() method of the parent class is invoked instead. The result is a fatal error.

A fatal error is thrown.

Downsides

An important downside is that this solution attempts to mimic the abstract class pattern. Abstract classes aren't supported in Swift and this solution is nothing more than an attempt to patch the absence of abstract classes in Swift.

A more important downside is that a missing implementation isn't detected at compile time. Which method is invoked is decided at runtime through dynamic dispatch. This is a major limitation of this solution.

Protocols

How It Works

Fortunately, Swift offers a much better alternative to the above solution, protocols. Protocols are nothing new if you're familiar with Objective-C. To illustrate this solution, I'd like to refactor the Animal, Cat, and Dog example with protocols.

We start by defining a protocol, Animal. The protocol defines a method, sound(). Remember that every method and property of a Swift protocol is required by default.

protocol Animal {
    func sound()
}

In the next step, we create the Cat and Dog classes. Both classes conform to the Animal protocol.

class Cat: Animal {

}

class Dog: Animal {

}

If we don't implement the sound() method in the Cat and Dog classes, the compiler throws an error at compile time.

Every method and property of a Swift protocol is required.

We can get rid of these errors by implementing the sound() method in Cat and Dog.

class Cat: Animal {
    func sound() {
        print("miauw")
    }
}

class Dog: Animal {
    func sound() {
        print("woof")
    }
}

Advantages

The advantage of protocols is that the compiler notifies us at compile time if the protocol hasn't been implemented correctly. Another advantage is that both classes and structures can conform to a protocol. This isn't true in Objective-C. Protocols bypass type restrictions. Any type can conform to the Animal protocol.

Protocol Extensions

In Swift 2, Apple introduced protocol extensions. They make protocols more powerful and versatile. With protocol extensions, developers can extend the functionality of a protocol by adding methods and computed properties. In the next example, we extend the Animal protocol by adding the maximumAge computed property and the feed() method.

extension Animal {
    var maximumAge: Int {
        return 20
    }

    func feed() {
        print("eating")
    }
}

With protocol extensions, developers can define behavior on protocols instead of on the types that conform to the protocol.

The functions and computed properties defined by a protocol extension do not need to be implemented by the types that conform to the protocol. This makes protocol extensions great for providing default implementations.

class Cat: Animal {
    func sound() {
        print("miauw")
    }
}

class Dog: Animal {
    func sound() {
        print("woof")
    }

    var maximumAge: Int {
        return 25
    }
}

let cat = Cat()
cat.maximumAge // 20
cat.feed()

let dog = Dog()
dog.maximumAge // 25
dog.feed()

What's Next?

While protocols are a great alternative to the abstract class pattern, it's important to remember that protocols don't attempt to mimic abstract classes. Protocol extensions are much more powerful than what we covered in this tutorial. We only scratched the surface. Questions? Leave them in the comments below or reach out to me on Twitter.