Swift Patterns

Abstract Classes

Swift Patterns
1 Namespaces 07:20
2 Abstract Classes 05:55
3 Builders 11:54
Download Your Free Copy of
The Missing Manual
for Swift Development

The Guide I Wish I Had When I Started Out

Join 20,000+ Developers Learning About Swift Development

Download Your Free Copy

The title of this episode is a bit misleading because the Swift programming language has no support for abstract classes. There are a few workarounds, though. In this episode, we take a look at two alternatives to the abstract class pattern in Swift.

Abstract Classes

How It Works

The first workaround attempts to mimic the abstract class pattern. Let's take a look at an example.

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 override 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 are required to override the `sound()` method.")
    }

}

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

class Animal {

    func sound() {
        fatalError("Subclasses are required to override the `sound()` method.")
    }

}

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? Notice that we removed the sound() method from the Dog class. The Dog subclass doesn't override the sound() method of its parent class, Animal. That 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 implements the sound() method, then that implementation is used since it overrides the implementation of the superclass. That is textbook class inheritance.

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.

Abstract Classes in Swift

Downsides

The first downside is that this solution attempts to mimic the abstract class pattern. Abstract classes are not supported in Swift and this solution is nothing more than an attempt to patch the absence of abstract classes in Swift.

The second, more important, downside is that a missing implementation isn't picked up by the compiler at compile time. Which method is invoked is decided at runtime through dynamic dispatch. That is a major risk and limitation of this solution.

Protocols

How It Works

The good news is that Swift offers a much better alternative to the above solution, protocols. Protocols and protocol-oriented programming (POP) are not new if you are familiar with Objective-C. Let me illustrate how it works by applying protocol-oriented programming to the Animal, Cat, and Dog example. It is time to refactor.

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()

}

The next step is implementing the Cat and Dog classes. Both classes conform to the Animal protocol.

protocol Animal {

    func sound()

}

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. That is exactly what we want. The compiler should warn us at compile time if the class don't conform to the protocol.

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.

protocol Animal {

    func sound()

}

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 a type doesn't conform to the protocol. Another advantage is that both reference types and value types can conform to a protocol. This isn't true in Objective-C. Protocols bypass type restrictions. Any type can conform to the Animal protocol, including structs and enums.

Let's apply that to the example. Cat and Dog don't need to be declared as classes. We can declare them as structs instead. Value types are an important concept to understand in Swift and I always default to a value type unless I have a good reason to use a reference type.

protocol Animal {

    func sound()

}

struct Cat: Animal {

    func sound() {
        print("miauw")
    }

}

struct Dog: Animal {

    func sound() {
        print("woof")
    }

}

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 this example, we extend the Animal protocol by adding the maximumAge computed property and the feed() method.

protocol Animal {

    func sound()

}

extension Animal {

    var maximumAge: Int {
       20
    }

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

}

struct Cat: Animal {

    func sound() {
        print("miauw")
    }

}

struct Dog: Animal {

    func sound() {
        print("woof")
    }

}

With protocol extensions, you can add implementations, such as methods and computed properties, to conforming types without having to make changes to the conforming types.

The methods and computed properties defined in a protocol extension don't need to be implemented by the types that conform to the protocol. This makes protocol extensions great for providing default implementations.

struct Cat: Animal {

    func sound() {
        print("miauw")
    }

}

struct Dog: Animal {

    func sound() {
        print("woof")
    }

    var maximumAge: Int {
       25
    }

}

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

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

Notice that Dog implements the maximumAge computed property while Cat doesn't.

What's Next?

While protocols are a great alternative to the abstract class pattern, it is important to understand and remember that protocols don't attempt to mimic abstract classes.

In this episode, we briefly touched on protocol extensions. They make protocols and protocol-oriented programming more powerful and versatile. We only scratched the surface, though. There is much more to explore.

Download Your Free Copy of
The Missing Manual
for Swift Development

The Guide I Wish I Had When I Started Out

Join 20,000+ Developers Learning About Swift Development

Download Your Free Copy
Next Episode "Builders"