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:
if number % 2 == 0 { ... }Schöner wäre es, wenn Int diese Fähigkeit selbst hätte, so wie text.isEmpty oder array.count:
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
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 Intheißt: „Füge dem TypIntFolgendes hinzu.“selfist der Wert selbst, also die Zahl, auf der duisEvenaufrufst.
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:
extension String {
func shout() -> String {
uppercased() + "!"
}
var wordCount: Int {
split(separator: " ").count
}
}
print("hallo welt".shout()) // HALLO WELT!
print("Swift macht Spaß".wordCount) // 3Innerhalb 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
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:
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:
#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:
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 initProtokolle 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:
struct Point {
var x: Int
var y: Int
}
extension Point: CustomStringConvertible {
var description: String {
"(\(x), \(y))"
}
}
extension Point: Equatable {} // Swift erzeugt == automatischUnd weil das bei jedem Typ geht, kannst du auch Apples Typen eigene Protokolle erfüllen lassen:
protocol Describable {
func describe() -> String
}
extension Int: Describable {
func describe() -> String {
"die Zahl \(self)"
}
}
print(42.describe()) // die Zahl 42Protocol 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:
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:
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
extension String {
var count: Int { 0 }
}Fehlermeldung
Invalid redeclaration of 'count'
Den Namen gibt es schon. Wähle einen eigenen.
self verwechselt
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
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:
extension Array where Element == Int {
var sum: Int { reduce(0, +) }
}
print([1, 2, 3].sum) // 6sum 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
Ü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!
extension Int {
var squared: Int {
self * self
}
}
print(5.squared) // 25Ü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!
extension String {
var isPalindrome: Bool {
let lower = lowercased()
return lower == String(lower.reversed())
}
}
print("Anna".isPalindrome) // true
print("Rentner".isPalindrome) // true
print("Swift".isPalindrome) // falseWeil die Eigenschaft hier aus zwei Zeilen besteht, ist return nötig.
Ü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!
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
extensionerweiterst 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: -undstatic let example. - Ein
initin 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
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())" }
}