Swift
Abstract Classes
Swift Programming
Object-Oriented Programming
Swift Development

Abstract classes in Swift Language

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Swift does not include an abstract keyword, so you cannot declare an abstract class the way you would in Java or C#. Even so, the underlying design need still exists: sometimes you want a shared base implementation while forcing conforming types to provide specific behavior. In Swift, that need is usually solved with protocols, protocol extensions, or a base class that intentionally refuses incomplete use.

Use Protocols When You Need a Contract

If your real goal is "every conforming type must implement these members," a protocol is usually the most idiomatic solution. Protocols give you compile-time enforcement, which is the biggest thing developers actually want from abstract classes.

swift
1protocol ReportExporter {
2    var fileExtension: String { get }
3    func export(data: [String]) -> Data
4}
5
6struct CSVExporter: ReportExporter {
7    let fileExtension = "csv"
8
9    func export(data: [String]) -> Data {
10        let body = data.joined(separator: ",")
11        return Data(body.utf8)
12    }
13}

This approach is preferable when inheritance is not required. The type can be a struct, enum, or class, and the compiler guarantees that the required members exist.

Add Shared Behavior with Protocol Extensions

One reason people reach for abstract classes is to share default implementation. In Swift, protocol extensions cover a large part of that use case without forcing everything into a class hierarchy.

swift
1protocol ReportExporter {
2    var fileExtension: String { get }
3    func export(data: [String]) -> Data
4}
5
6extension ReportExporter {
7    func outputFileName(for baseName: String) -> String {
8        "\(baseName).\(fileExtension)"
9    }
10}
11
12struct JSONExporter: ReportExporter {
13    let fileExtension = "json"
14
15    func export(data: [String]) -> Data {
16        let json = "[\"" + data.joined(separator: "\",\"") + "\"]"
17        return Data(json.utf8)
18    }
19}

Now every exporter gets the shared file-name logic, while each concrete type still provides its own output format. This often produces a cleaner design than a traditional abstract base class because the shared implementation is separated from object identity and inheritance.

Use a Base Class When Shared State Matters

There are still cases where a class is the right tool. If multiple subclasses must share stored properties, lifecycle hooks, or framework inheritance, a base class can simulate abstract behavior.

swift
1class BaseJob {
2    let id: String
3
4    init(id: String) {
5        self.id = id
6    }
7
8    func run() {
9        preconditionFailure("Subclasses must override run()")
10    }
11}
12
13final class EmailJob: BaseJob {
14    override func run() {
15        print("Sending email for job \(id)")
16    }
17}

This pattern is common in UIKit-heavy code because classes are already required in many framework APIs. The tradeoff is important, though: the compiler does not stop you from instantiating BaseJob. The failure only appears at runtime if the incomplete implementation is used.

When a Fake Abstract Class Is Reasonable

A simulated abstract base class can still be a good choice when:

  • a framework already requires inheritance
  • subclasses share stored state
  • you need a shared initializer chain
  • object identity matters

A common example is a custom base view controller:

swift
1import UIKit
2
3class BaseScreenController: UIViewController {
4    func screenTitle() -> String {
5        preconditionFailure("Subclasses must override screenTitle()")
6    }
7
8    override func viewDidLoad() {
9        super.viewDidLoad()
10        navigationItem.title = screenTitle()
11    }
12}

Here, the framework already expects a class hierarchy, so a partially abstract base class is a practical compromise.

Prevent Misuse as Much as Possible

Because Swift cannot mark the class abstract, you should reduce accidental misuse in other ways. Use access control to hide the base type where possible. Document the expectation clearly. Prefer factories or dependency injection that expose only concrete types or protocols to the rest of the app.

If you rely only on fatalError or preconditionFailure, the design problem is not enforced until runtime. That is acceptable in a narrow framework boundary, but it is not a great default for general domain models.

Common Pitfalls

One common mistake is recreating abstract classes everywhere just because that pattern was normal in another language. In Swift, protocols often solve the problem more directly and with better compiler checks.

Another mistake is using a base class with many "must override" methods when composition would be simpler. Deep inheritance trees get brittle quickly.

Developers also sometimes treat protocol extensions as if they were real stored inheritance. They are not. Protocol extensions can share behavior, but they cannot provide stored properties.

Finally, be careful when base-class methods trap by design. Tests, previews, or storyboards can instantiate incomplete types accidentally if the API is too open.

Summary

  • Swift has no built-in abstract class feature.
  • Protocols are the best tool when you need required behavior with compile-time enforcement.
  • Protocol extensions provide shared default behavior without forcing inheritance.
  • Base classes are still useful when you need shared state or framework-driven inheritance.
  • If you simulate abstract methods with runtime traps, keep that pattern narrow and intentional.

Course illustration
Course illustration

All Rights Reserved.