Start learning free

LESSON 408 OF 415 · OBJECT-ORIENTED PROGRAMMING

Abstract Classes in Swift: The fatalError Pattern

Lesson Read the full explanation

TL ; DR:

An abstract class is a class you can't instantiate directly — it's a half-finished template. It can declare methods with no body (abstract methods) that any concrete subclass must implement. Swift has no abstract keyword, so the usual convention is a parent method whose body calls fatalError.


class Animal {
    func speak() {                            // placeholder body
        fatalError("Subclass must override")  // subclasses must provide one
    }
}
class Dog: Animal {
    override func speak() {
        print("Woof")
    }
}


Full lesson:

Sometimes you want to capture shared behavior across a family of classes without committing to one concrete implementation. For example, every Animal can speak(), but exactly how depends on whether it's a Dog or a Cat. That's what abstract classes are for.

Two superpowers

  1. In languages with real abstract classes you can't write Animal() directly. In Swift nothing stops you, but calling speak() on a plain Animal crashes at run time, so you create a concrete subclass instead.
  2. In languages with real abstract classes, a subclass must implement every abstract method. In Swift the compiler does not check this: a subclass that forgets to override speak() compiles, and crashes when speak() is called. (A protocol gives a compile-time contract; you'll meet it in the next lessons.)

Mixing abstract and concrete

An abstract class can also have concrete methods (with bodies) and fields. Those are shared by all subclasses for free, just like normal inheritance. Only the placeholder methods need an override.

Notes by language

  • Swift — no abstract keyword. Use a parent method that calls fatalError, or a protocol when you want the compiler to enforce the contract.
  • C# / Java — keyword abstract on the class and on the method.
  • Python — inherit from abc.ABC and decorate the method with @abstractmethod.

Your task

The abstract-like Animal class is already defined. Create a concrete Dog subclass and implement the speak method so it prints "Woof". The harness builds a Dog and calls speak().

Expected output:

Woof
STEP 1 OF 2
    Ctrl + Enter to check

    SWIFT · OBJECT-ORIENTED PROGRAMMING

    Abstract Classes in Swift, explained

    What an abstract class is, and what Swift has instead

    In many languages an abstract class is a half-finished template. It cannot be instantiated, and it declares abstract methods with no body that every concrete subclass must implement. C# and Java have an abstract keyword for this.

    Swift has no such keyword. It has no way to mark a class as "cannot be created", and no way to mark a method as "subclasses must implement this" for a class. Instead, Swift programmers use two substitutes:

    • a base class whose method stops the program with fatalError if a subclass does not override it;
    • a protocol, which is a real compile-time contract (the next lesson covers it).

    This lesson uses the first one. It gives you the shape of an abstract class, but the checking happens when the program runs, not when it compiles.

    The base class as a template

    The parent declares the method, and its body says "a subclass must replace me":

    class Animal {
        func speak() { fatalError("must override") }
    }
    

    fatalError stops the program immediately with the message you give it. Because it never returns, the method needs no other statement.

    A subclass supplies the real behavior with override:

    class Dog: Animal {
        override func speak() {
            print("Woof")
        }
    }
    

    Now Dog().speak() prints Woof, and so does a Dog stored in an Animal variable, because Swift runs the subclass's version of a class method.

    What Swift does not enforce

    Two guarantees that real abstract classes give you do not exist here. Both are easy to test:

    1. You can still create the parent. let bare = Animal() compiles and runs. Nothing happens until someone calls speak() on it.
    2. A subclass can forget the method. With class Silent: Animal {} the code compiles. Calling Silent().speak() then reaches the parent's body and the program crashes with Fatal error: must override.

    The crash is the "contract with teeth", but it bites at run time. If a rarely used code path forgets an override, the mistake can reach users. When you need the compiler to reject an incomplete type, use a protocol: a class that adopts a protocol without implementing a requirement does not compile.

    The lesson's exercise

    The starter code gives you Animal and a Solution class whose mainFunction creates a Dog and calls speak(). The Dog class does not exist yet, so the code does not compile. The expected output is Woof.

    1. Declare Dog as a subclass of Animal. Put it between Animal and Solution, with : Animal after the name. Until the class exists, Dog() in mainFunction is a compile error.
    2. Override speak. Inside Dog, write override func speak() and print Woof. If you leave this out, the code compiles, but rex.speak() reaches fatalError and crashes.
    class Animal {
        func speak() { fatalError("must override") }
    }
    
    class Dog: Animal {
        override func speak() {
            print("Woof")
        }
    }
    
    class Solution {
        func mainFunction() {
            let rex = Dog()
            rex.speak()
        }
    }
    

    The override keyword is required whenever a subclass replaces an inherited method. Leaving it out is a compile error.

    Shared behavior and a protocol alternative

    A real abstract class mixes abstract and concrete methods. The same works with the pattern above: the parent can have ordinary methods and stored properties that every subclass inherits, and only the methods with fatalError need an override.

    The compile-time version uses a protocol with a default implementation in an extension:

    protocol Animal {
        var name: String { get }
        func sound() -> String
    }
    
    extension Animal {
        func speak() { print("\(name): \(sound())") }
    }
    
    struct Dog: Animal {
        let name: String
        func sound() -> String { "Woof" }
    }
    
    Dog(name: "Rex").speak()
    
    Rex: Woof
    

    Here sound() is the abstract part. Leaving it out of Dog is a compile error, speak() is the shared concrete part, and Animal() cannot be written at all. The price is that protocols cannot hold stored properties for you or be subclassed.

    Key takeaways

    • Swift has no abstract keyword. The class version is a parent method that calls fatalError.
    • Nothing stops Animal(), and nothing forces a subclass to override. A missing override shows up as a crash when the method is called.
    • Subclasses replace the method with override func.
    • For a contract the compiler enforces, use a protocol.
    Keep going with SwiftAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free