Lektion 1.16 · Stufe 1 – Swift-Fundament

Extensions

ca. 15 Minuten Lesezeit

Lernziel

Nach dieser Lektion kannst du bestehende Typen, auch Apples eigene wie String und Int, mit extension um eigene Eigenschaften und Methoden erweitern, Protokollerfüllungen sauber auslagern und mit Protocol Extensions Standardverhalten für viele Typen gleichzeitig bereitstellen.

Das Problem: Typen, die man nicht ändern kann

Du prüfst in deiner App an vielen Stellen, ob eine Zahl gerade ist:

Swift
if number % 2 == 0 { ... }

Schöner wäre es, wenn Int diese Fähigkeit selbst hätte, so wie text.isEmpty oder array.count:

Swift
if number.isEven { ... }

Aber Int hast nicht du geschrieben, sondern Apple. Du kannst den Code nicht öffnen und ändern. Oder doch?

Mit einer Extension kannst du jeden Typ um neue Fähigkeiten erweitern, egal wer ihn geschrieben hat.

Eine Extension schreiben

Swift
extension Int {
    var isEven: Bool {
        self % 2 == 0
    }
}

print(4.isEven)    // true
print(7.isEven)    // false

let number = 10
if number.isEven {
    print("\(number) ist gerade")
}
  • extension Int heißt: „Füge dem Typ Int Folgendes hinzu.“
  • self ist der Wert selbst, also die Zahl, auf der du isEven aufrufst.

Ab jetzt hat jeder Int in deinem Projekt die Eigenschaft isEven, als hätte Apple sie eingebaut.

Methoden hinzufügen

Genauso kannst du Methoden ergänzen:

Swift
extension String {
    func shout() -> String {
        uppercased() + "!"
    }

    var wordCount: Int {
        split(separator: " ").count
    }
}

print("hallo welt".shout())                    // HALLO WELT!
print("Swift macht Spaß".wordCount)            // 3

Innerhalb der Extension kannst du direkt auf alle anderen Eigenschaften und Methoden des Typs zugreifen, hier uppercased() und split(...), ohne self. davor.

Was Extensions dürfen und was nicht

Extensions dürfen hinzufügen:

  • ✅ berechnete Eigenschaften
  • ✅ Methoden (auch mutating)
  • ✅ zusätzliche Initializer
  • ✅ Protokollerfüllungen

Extensions dürfen nicht:

  • ❌ gespeicherte Eigenschaften hinzufügen
  • ❌ bestehende Methoden ersetzen
Swift
extension String {
    var nickname = ""
}

Fehlermeldung

Extensions must not contain stored properties

Der Grund: Eine gespeicherte Eigenschaft würde den Typ größer machen. Jeder String in deinem Programm bräuchte plötzlich mehr Speicher. Eine berechnete Eigenschaft braucht dagegen keinen Speicherplatz, sie wird ja bei Bedarf berechnet.

Eigene Typen ordnen

Extensions sind nicht nur für fremde Typen nützlich. Viele Entwickler nutzen sie, um eigenen Code übersichtlich zu gliedern:

Swift
struct Book {
    let title: String
    let pages: Int
}

// MARK: - Berechnete Eigenschaften
extension Book {
    var readingTime: Int { pages * 2 }
    var isLong: Bool { pages > 300 }
}

// MARK: - Beispieldaten
extension Book {
    static let example = Book(title: "Momo", pages: 304)
}

So steht im eigentlichen struct nur das Wesentliche, die gespeicherten Daten, und alles andere ist thematisch sortiert.

MARK-Kommentare

// MARK: - Titel ist ein besonderer Kommentar. Xcode zeigt ihn in der Leiste über dem Editor als Überschrift an. Klick oben auf den Dateinamen-Pfad, und du siehst eine Gliederung deiner Datei. Bei langen Dateien ist das enorm praktisch.

Beispieldaten für Previews

Das Muster static let example wirst du in SwiftUI ständig verwenden. Previews brauchen Beispieldaten, und so hast du sie immer griffbereit:

Swift
#Preview {
    BookDetailView(book: .example)
}

Eigener Initializer, ohne den automatischen zu verlieren

Erinnerst du dich an das 🟡-FAQ aus Lektion 1.12? Schreibst du in einer Struct einen eigenen init, verschwindet der automatische. In einer Extension passiert das nicht:

Swift
struct Temperature {
    var celsius: Double
}

extension Temperature {
    init(fahrenheit: Double) {
        celsius = (fahrenheit - 32) * 5 / 9
    }
}

let a = Temperature(celsius: 20)        // automatischer init
let b = Temperature(fahrenheit: 68)     // eigener init

Protokolle per Extension erfüllen

Du kannst die Erfüllung eines Protokolls in eine eigene Extension auslagern. Das macht sichtbar, welcher Code zu welchem Protokoll gehört:

Swift
struct Point {
    var x: Int
    var y: Int
}

extension Point: CustomStringConvertible {
    var description: String {
        "(\(x), \(y))"
    }
}

extension Point: Equatable {}   // Swift erzeugt == automatisch

Und weil das bei jedem Typ geht, kannst du auch Apples Typen eigene Protokolle erfüllen lassen:

Swift
protocol Describable {
    func describe() -> String
}

extension Int: Describable {
    func describe() -> String {
        "die Zahl \(self)"
    }
}

print(42.describe())   // die Zahl 42

Protocol Extensions: Standardverhalten für alle

Jetzt kommt das mächtigste Werkzeug: Du kannst nicht nur einen Typ erweitern, sondern ein Protokoll. Dann bekommt jeder Typ, der das Protokoll erfüllt, das neue Verhalten automatisch:

Swift
protocol Animal {
    var name: String { get }
    func sound() -> String
}

extension Animal {
    func introduce() -> String {
        "\(name) sagt \(sound())"
    }
}

struct Dog: Animal {
    let name: String
    func sound() -> String { "Wuff!" }
}

struct Cat: Animal {
    let name: String
    func sound() -> String { "Miau!" }
}

print(Dog(name: "Bello").introduce())   // Bello sagt Wuff!
print(Cat(name: "Minka").introduce())   // Minka sagt Miau!

Dog und Cat haben introduce() nie selbst geschrieben. Sie bekommen die Methode geschenkt, weil sie Animal erfüllen.

Standardimplementierung, die man überschreiben kann

Eine Protocol Extension kann auch eine Anforderung des Protokolls mit einem Standard versehen:

Swift
extension Animal {
    func sound() -> String {
        "..."
    }
}

struct Fish: Animal {
    let name: String
    // sound() muss nicht geschrieben werden, der Standard wird verwendet
}

print(Fish(name: "Nemo").introduce())   // Nemo sagt ...

Dog und Cat behalten ihr eigenes sound(), Fish verwendet den Standard.

So funktionieren SwiftUI-Modifier

Hast du dich gefragt, warum jede View .padding(), .font(...) und Hunderte andere Modifier hat, obwohl du sie in deiner eigenen View nie geschrieben hast? Genau so: Apple hat sie in einer extension View definiert. Jeder Typ, der View erfüllt, also auch deine eigene ContentView, bekommt sie automatisch. In Lektion 3.28 schreibst du deine eigenen Modifier auf diese Weise.

Typische Fehler

Gespeicherte Eigenschaft in Extension

Fehlermeldung

Extensions must not contain stored properties

Mach daraus eine berechnete Eigenschaft oder speichere den Wert im Typ selbst.

Name schon vergeben

Swift
extension String {
    var count: Int { 0 }
}

Fehlermeldung

Invalid redeclaration of 'count'

Den Namen gibt es schon. Wähle einen eigenen.

self verwechselt

Swift
extension Int {
    var squared: Int { Int * Int }
}

Fehlermeldung

Binary operator '*' cannot be applied to two 'Int.Type' operands

Int ist der Typ, self ist der Wert. Richtig: self * self.

mutating vergessen

Swift
extension Int {
    func double() {
        self *= 2
    }
}

Fehlermeldung

Left side of mutating operator isn't mutable: 'self' is immutable

Ändert eine Methode den Wert selbst, braucht sie bei Werttypen mutating func.

FAQ

Anfänger

Was bedeutet das überhaupt?

Wo schreibe ich Extensions hin? Überall dort, wo es passt. Extensions für eigene Typen stehen meist in derselben Datei wie der Typ. Allgemeine Extensions für String, Int und Co. sammelt man oft in einer eigenen Datei, etwa String+Helpers.swift.

Verändere ich damit Apples Code? Nein. Deine Extension gilt nur in deinem Projekt. Apples Original bleibt unberührt.

Ist eine Extension dasselbe wie Vererbung? Nein. Vererbung erzeugt einen neuen Typ, der vom alten abstammt. Eine Extension erweitert den bestehenden Typ selbst. Alle Int-Werte bekommen isEven, nicht nur eine Unterart davon.

Fortgeschritten

Warum verhält es sich so?

Kann ich eine Extension nur für bestimmte Fälle schreiben? Ja, mit where:

Swift
extension Array where Element == Int {
    var sum: Int { reduce(0, +) }
}

print([1, 2, 3].sum)   // 6

sum gibt es dann nur für Arrays aus Int, nicht für [String].

Sollte ich viele Extensions für String schreiben? Mit Maß. Kleine, eindeutig benannte Helfer sind großartig. Wer String aber Dutzende eigene Methoden gibt, macht es anderen schwer zu erkennen, was von Apple kommt und was nicht.

Profi

Was passiert unter der Haube?

Warum ruft manchmal die Protocol Extension statt der eigenen Methode auf? Steht eine Methode nur in der Protocol Extension, aber nicht als Anforderung im Protokoll selbst, wird sie statisch ausgewählt: anhand des Typs, den der Compiler sieht. Hältst du einen Dog als any Animal, wird dann die Version aus der Extension verwendet, selbst wenn Dog eine eigene hat. Steht sie dagegen im Protokoll, wird dynamisch die passende Version gewählt. Faustregel: Soll ein Typ eine Methode überschreiben können, gehört sie als Anforderung ins Protokoll.

Was ist „retroaktive Modellierung“? Das Erfüllen eines Protokolls durch einen fremden Typ im Nachhinein, wie extension Int: Describable. Erfüllst du ein fremdes Protokoll für einen fremden Typ, warnt Swift inzwischen davor. Würde Apple dieselbe Erfüllung später selbst einbauen, gäbe es zwei widersprüchliche Versionen.

Übungen

Anfänger

Übung 1: Quadrat

Erweitere Int um eine berechnete Eigenschaft squared, die das Quadrat der Zahl liefert. Teste sie mit 5.squared.

Lösung Übung 1Erst selbst probieren!
Swift
extension Int {
    var squared: Int {
        self * self
    }
}

print(5.squared)   // 25
Fortgeschritten

Übung 2: String-Helfer

Erweitere String um eine Eigenschaft isPalindrome, die prüft, ob ein Wort vorwärts und rückwärts gleich gelesen wird, ohne Rücksicht auf Groß- und Kleinschreibung. „Anna“ und „Rentner“ sind Palindrome, „Swift“ nicht.

Tipp: String(text.reversed()) dreht einen Text um.

Lösung Übung 2Erst selbst probieren!
Swift
extension String {
    var isPalindrome: Bool {
        let lower = lowercased()
        return lower == String(lower.reversed())
    }
}

print("Anna".isPalindrome)      // true
print("Rentner".isPalindrome)   // true
print("Swift".isPalindrome)     // false

Weil die Eigenschaft hier aus zwei Zeilen besteht, ist return nötig.

Profi

Übung 3: Protocol Extension

Definiere ein Protokoll Priced mit var price: Double { get }. Ergänze per Protocol Extension eine Eigenschaft priceWithTax (20 % Steuer) und eine Methode formattedPrice(), die z. B. „12.0 €“ liefert. Lass zwei verschiedene Structs, etwa Coffee und Ticket, das Protokoll erfüllen, und zeig, dass beide die neuen Fähigkeiten haben.

Lösung Übung 3Erst selbst probieren!
Swift
protocol Priced {
    var price: Double { get }
}

extension Priced {
    var priceWithTax: Double {
        price * 1.2
    }

    func formattedPrice() -> String {
        "\(priceWithTax) €"
    }
}

struct Coffee: Priced {
    let name: String
    let price: Double
}

struct Ticket: Priced {
    let event: String
    let price: Double
}

print(Coffee(name: "Melange", price: 3.5).formattedPrice())   // 4.2 €
print(Ticket(event: "Konzert", price: 45).formattedPrice())   // 54.0 €

Zusammenfassung

  • Mit extension erweiterst du jeden Typ um neue Fähigkeiten, auch Apples.
  • Erlaubt: berechnete Eigenschaften, Methoden, Initializer, Protokollerfüllungen. Nicht erlaubt: gespeicherte Eigenschaften.
  • Extensions helfen, eigenen Code zu gliedern, etwa mit // MARK: - und static let example.
  • Ein init in einer Extension lässt den automatischen Initializer bestehen.
  • Protocol Extensions geben allen Typen eines Protokolls Standardverhalten.
  • So bekommen alle Views ihre Modifier wie .padding() und .font(...).

Spickzettel

Swift
extension Int {
    var isEven: Bool { self % 2 == 0 }
}

extension Book {
    static let example = Book(title: "Momo", pages: 304)
}

extension Point: Equatable {}

extension Animal {
    func introduce() -> String { "\(name) sagt \(sound())" }
}

Lektion geschafft?

Markiere sie als erledigt, damit du deinen Fortschritt siehst.