Structs, Classes & Enums
Die drei Wege, in Swift eigene Typen zu definieren. Value vs. Reference Semantics, Methoden, Mutating und Enums mit Assoziierten Werten.
Inhaltsverzeichnis
Structs, Classes & Enums
In Swift hast du drei Optionen, eigene Typen zu bauen. Welchen du waehlst, macht einen grossen Unterschied.
Structs - Value Types
Structs sind in Swift die bevorzugte Wahl fuer Datenmodelle:
struct Person {
let name: String
var alter: Int
}
let anna = Person(name: "Anna", alter: 28)
Swift generiert automatisch einen Memberwise Initializer - du musst keinen Konstruktor schreiben.
Value Semantics
var a = Person(name: "Anna", alter: 28)
var b = a // KOPIE
b.alter = 29
print(a.alter) // 28 - a ist unveraendert
print(b.alter) // 29
Zuweisungen machen eine Kopie. Das verhindert versehentliche Seiteneffekte.
Methoden in Structs
struct Rechteck {
var breite: Double
var hoehe: Double
func flaeche() -> Double {
breite * hoehe
}
mutating func skalieren(um faktor: Double) {
breite *= faktor
hoehe *= faktor
}
}
var r = Rechteck(breite: 3, hoehe: 4)
print(r.flaeche()) // 12
r.skalieren(um: 2)
print(r.flaeche()) // 48
Wichtig: Methoden, die die Struct aendern, brauchen mutating. Und die Instanz muss var sein - nicht let.
Computed Properties
Properties, die einen Wert berechnen:
struct Rechteck {
var breite: Double
var hoehe: Double
var flaeche: Double {
breite * hoehe
}
var umfang: Double {
2 * (breite + hoehe)
}
}
let r = Rechteck(breite: 3, hoehe: 4)
print(r.flaeche) // 12 - wie ein Property, nicht wie eine Methode
Classes - Reference Types
Classes sehen aehnlich aus, verhalten sich aber anders:
class Konto {
var saldo: Double
init(startSaldo: Double) {
self.saldo = startSaldo
}
func einzahlen(_ betrag: Double) {
saldo += betrag
}
}
let a = Konto(startSaldo: 100)
let b = a // KEINE Kopie - b zeigt aufs gleiche Objekt
b.einzahlen(50)
print(a.saldo) // 150 - auch a sieht die Aenderung
Classes brauchen init
Keine automatischen Memberwise Initializer - du musst einen Konstruktor schreiben.
Keine mutating-Methoden
Classes koennen Felder immer aendern - kein mutating noetig.
Struct oder Class?
Faustregeln aus der Swift-Praxis:
- Struct ist die Default-Wahl - fuer Datenmodelle, DTOs, Einstellungen, Werte
- Class wenn du:
- Vererbung brauchst (Classes erben, Structs nicht)
- Identity wichtig ist (selbes Objekt, nicht gleicher Wert)
- mit objektorientierten APIs (z.B. UIKit) arbeitest
In SwiftUI-Code siehst du fast nur Structs - das ist kein Zufall.
Vererbung (nur bei Classes)
class Tier {
let name: String
init(name: String) { self.name = name }
func geraeusch() -> String {
"..."
}
}
class Hund: Tier {
override func geraeusch() -> String {
"Wuff!"
}
}
class Katze: Tier {
override func geraeusch() -> String {
"Miau!"
}
}
let tiere: [Tier] = [Hund(name: "Bello"), Katze(name: "Lilli")]
for tier in tiere {
print("\(tier.name): \(tier.geraeusch())")
}
class Hund: Tier-Hunderbt vonTieroverrideist Pflicht, wenn du eine Methode ueberschreibst
final - Vererbung verbieten
final class Konfiguration { /* ... */ }
Enums
Enums in Swift sind sehr maechtig - weit mehr als nur Konstanten.
Einfach
enum Richtung {
case nord
case sued
case ost
case west
}
var r = Richtung.nord
r = .west // Kurzschreibweise, wenn Typ klar ist
Enums mit Raw Values
enum Statuscode: Int {
case ok = 200
case notFound = 404
case serverError = 500
}
let s = Statuscode.notFound
print(s.rawValue) // 404
let vom404 = Statuscode(rawValue: 404) // Optional<Statuscode>
Enums mit Associated Values (Superkraft!)
enum Event {
case klick(x: Double, y: Double)
case tastendruck(Character)
case schliessen
}
let e = Event.klick(x: 12.5, y: 48.3)
switch e {
case .klick(let x, let y):
print("Klick bei (\(x), \(y))")
case .tastendruck(let c):
print("Taste: \(c)")
case .schliessen:
print("Schliessen")
}
Jeder Case kann eigene Daten tragen - wie Sum Types in Rust oder Haskell.
Enums mit Methoden
enum Richtung {
case nord, sued, ost, west
var gegenueber: Richtung {
switch self {
case .nord: return .sued
case .sued: return .nord
case .ost: return .west
case .west: return .ost
}
}
}
print(Richtung.nord.gegenueber) // sued
Optional unter der Haube
Der bekannte Optional-Typ ist ein Enum:
enum Optional<Wrapped> {
case none
case some(Wrapped)
}
Jetzt verstehst du, warum du .some(5) oder .none schreiben kannst.
struct vs class vs enum - Kurzuebersicht
| Feature | Struct | Class | Enum |
|---|---|---|---|
| Value Semantics | โ | โ | โ |
| Reference Semantics | โ | โ | โ |
| Vererbung | โ | โ | โ |
| Methoden | โ | โ | โ |
| Computed Properties | โ | โ | โ |
| Stored Properties | โ | โ | โ |
| Associated Values | โ | โ | โ |
Protocols - kurzer Vorblick
Protokolle beschreiben Faehigkeiten, die Typen implementieren koennen:
protocol Fliegend {
func fliegen()
}
struct Vogel: Fliegend {
func fliegen() {
print("Flatter flatter")
}
}
Das ist Swifts Protocol-Oriented Programming, das wir in einem spaeteren Kapitel vertiefen.
Praktisches Beispiel
struct Produkt {
let name: String
let preis: Double
}
struct Warenkorb {
private(set) var artikel: [Produkt] = []
var gesamt: Double {
artikel.reduce(0) { $0 + $1.preis }
}
mutating func hinzufuegen(_ p: Produkt) {
artikel.append(p)
}
}
var korb = Warenkorb()
korb.hinzufuegen(Produkt(name: "Buch", preis: 19.99))
korb.hinzufuegen(Produkt(name: "Kaffee", preis: 3.50))
print("Gesamt: \(korb.gesamt)") // 23.49
private(set) erlaubt Lesen von aussen, Schreiben nur von innen.
Zusammenfassung
- Structs sind die bevorzugte Wahl - Value Semantics, automatischer Init
- Classes haben Reference Semantics und Vererbung
- Enums sind in Swift Sum Types mit Associated Values
mutatingbei Methoden, die Structs/Enums aendern- Computed Properties fuer abgeleitete Werte
Damit hast du das Fundament, um eigene Swift-Typen zu modellieren. Im weiteren Kurs gehen wir auf Protocols, generische Programmierung, Fehlerbehandlung und async/await ein.