Swiftlang
Art
Lektion 7 von 12
Niveau
Grundlagen
Lesezeit
etwa 4 Minuten
Stand

Structs, Klassen, Enums

Ein struct ist ein Werttyp: Jede Zuweisung erzeugt eine unabhängige Kopie. Eine class ist ein Referenztyp: Zuweisungen teilen dasselbe Objekt. In Swift ist struct die Standardwahl, class nimmst du, wenn mehrere Stellen dasselbe Objekt mit Identität teilen müssen. Ein enum beschreibt eine feste Menge von Fällen, jeder Fall kann eigene Werte tragen.

Danach kannst du

  • erklärst den Unterschied zwischen Wert- und Referenztypen an einem Beispiel
  • entscheidest begründet zwischen struct und class
  • schreibst mutating-Methoden und kontrollierst Zugriff mit private(set)
  • modellierst Zustände mit Enums und zugehörigen Werten

Voraussetzungen

Umgebung

Swift 6.4.0, Sprachmodus 6
plattformunabhängig, geprüft unter Linux x86-64

Werttyp und Referenztyp

Structs und Klassen sehen auf den ersten Blick fast gleich aus: Beide haben Eigenschaften, Methoden und Initialisierer. Der entscheidende Unterschied zeigt sich erst bei einer Zuweisung.

struct-vs-class.swiftAusgeführt Swift 6.4.0, Linux x86-64, 19.10.2026
struct PunktWert {
    var x: Int
    var y: Int
}

final class PunktReferenz {
    var x: Int
    var y: Int
    init(x: Int, y: Int) {
        self.x = x
        self.y = y
    }
}

// struct: Zuweisung kopiert den Wert
var a = PunktWert(x: 1, y: 1)
var b = a
b.x = 99
print("struct:", a.x, b.x)

// class: Zuweisung kopiert nur die Referenz
let c = PunktReferenz(x: 1, y: 1)
let d = c
d.x = 99
print("class:", c.x, d.x)
print("dasselbe Objekt?", c === d)

Ausgabe

struct: 1 99
class: 99 99
dasselbe Objekt? true

Bei struct hat b = a den Punkt kopiert. Danach sind es zwei unabhängige Werte, und b.x = 99 lässt a unberührt. Bei class hat d = c nur den Verweis kopiert. c und d zeigen auf dasselbe Objekt, wie c === d bestätigt. Eine Änderung über d sieht man deshalb auch über c.

Werttyp und Referenztyp im Vergleich Links: zwei Variablen a und b vom Typ struct mit je einem eigenen Wert, x gleich 1 und x gleich 99. Rechts: zwei Konstanten c und d, die beide auf dasselbe Objekt mit x gleich 99 zeigen. struct: zwei Werte a x: 1 b x: 99 class: ein Objekt c d x: 99 b.x = 99 ändert nur b. d.x = 99 ändert das Objekt, das auch c sieht.
Nach b.x = 99 und d.x = 99: Werttypen sind unabhängig, Referenzen teilen sich ein Objekt.

Neben struct sind auch enum, Tupel und alle Collections der Standardbibliothek Werttypen. String, Array und Dictionary verhalten sich also wie der Punkt links. Das hast du in der Lektion zu Collections schon gesehen.

struct oder class?

Apple empfiehlt in Choosing Between Structures and Classes, standardmäßig Structs zu verwenden. Die Begründung ist praktisch: Bei einem Werttyp kann keine andere Stelle im Programm deine Daten unbemerkt ändern. Das macht Code leichter nachvollziehbar und ist bei Nebenläufigkeit ein großer Vorteil.

Eine Klasse nimmst du, wenn

  • mehrere Stellen dasselbe Ding mit eigener Identität teilen sollen, etwa eine Verbindung oder einen Cache,
  • du Vererbung brauchst, zum Beispiel weil ein Framework eine Basisklasse vorgibt,
  • du mit Objective-C-Code zusammenarbeitest.

final class im Beispiel verhindert, dass jemand von der Klasse erbt. Das ist eine gute Voreinstellung, solange du Vererbung nicht ausdrücklich brauchst.

mutating: ein Struct ändert sich

Methoden eines Structs dürfen dessen Eigenschaften normalerweise nicht ändern. Soll eine Methode das tun, markierst du sie mit mutating:

mutating.swiftAusgeführt Swift 6.4.0, Linux x86-64, 19.10.2026
struct Zaehler {
    private(set) var stand = 0

    mutating func erhoehe(um schritt: Int = 1) {
        stand += schritt
    }
}

var z = Zaehler()
z.erhoehe()
z.erhoehe(um: 5)
print(z.stand)

Ausgabe

6

private(set) erlaubt allen, den Stand zu lesen, aber nur dem Struct selbst, ihn zu ändern. So bleibt erhoehe der einzige Weg, den Zähler zu verändern. Fehlt mutating, sagt dir der Compiler, was zu tun ist:

mutating-fehlt.swiftKompiliert absichtlich nicht Meldung von Swift 6.4.0, Linux x86-64, 19.10.2026
struct Zaehler {
    var stand = 0

    func erhoehe() {
        stand += 1
    }
}

Compiler-Meldung

mutating-fehlt.swift:5:15: error: left side of mutating operator isn't mutable: 'self' is immutable
2 |     var stand = 0
3 | 
4 |     func erhoehe() {
  |     `- note: mark method 'mutating' to make 'self' mutable
5 |         stand += 1
  |               `- error: left side of mutating operator isn't mutable: 'self' is immutable
6 |     }
7 | }

Eine mutating-Methode kannst du nur auf einer Variable aufrufen. Auf einer let-Konstante eines Structs ist jede Änderung verboten, auch über Methoden. Bei einer Klasse ist das anders: let fixiert dort nur den Verweis, nicht das Objekt. Deshalb konnte im ersten Beispiel d.x = 99 trotz let d geschrieben werden.

Enums mit zugehörigen Werten

Ein enum beschreibt eine feste Auswahl. Jeder Fall kann eigene Werte mitbringen, die nur für diesen Fall gelten:

enum.swiftAusgeführt Swift 6.4.0, Linux x86-64, 19.10.2026
enum Zahlung {
    case bar
    case karte(letzteZiffern: String)
    case gutschein(code: String, wert: Double)
}

func beleg(_ zahlung: Zahlung) -> String {
    switch zahlung {
    case .bar:
        return "Bar bezahlt"
    case .karte(let ziffern):
        return "Karte •••• \(ziffern)"
    case let .gutschein(code, wert):
        return "Gutschein \(code) über \(wert) €"
    }
}

let zahlungen: [Zahlung] = [.bar, .karte(letzteZiffern: "4821"), .gutschein(code: "SWIFT10", wert: 10)]
for zahlung in zahlungen {
    print(beleg(zahlung))
}

// Rohwerte und CaseIterable
enum Wochentag: Int, CaseIterable {
    case montag = 1, dienstag, mittwoch, donnerstag, freitag, samstag, sonntag
}
print(Wochentag.mittwoch.rawValue)
print(Wochentag(rawValue: 7) == .sonntag)
print(Wochentag.allCases.count)

Ausgabe

Bar bezahlt
Karte •••• 4821
Gutschein SWIFT10 über 10.0 €
3
true
7

Eine Kartenzahlung hat Ziffern, ein Gutschein hat Code und Wert, eine Barzahlung hat nichts davon. Mit Optionals in einem einzigen Struct müsstest du immer prüfen, welche Felder gerade gelten. Das Enum macht ungültige Kombinationen unmöglich: Es gibt keinen Gutschein ohne Code.

Zusammen mit dem vollständigen switch aus der Lektion zum Kontrollfluss ist das eines der stärksten Werkzeuge in Swift. Fügst du einen neuen Zahlungsweg hinzu, meldet der Compiler jede Funktion, die ihn noch nicht behandelt.

Das zweite Enum zeigt Rohwerte: Jeder Fall hat einen festen Int. Wochentag(rawValue: 7) liefert ein Optional, denn nicht jede Zahl ist ein Wochentag. CaseIterable liefert alle Fälle als Collection.

Häufige Fehler

left side of mutating operator isn't mutable: 'self' is immutable. Eine Methode ändert eine Eigenschaft ohne mutating.

cannot use mutating member on immutable value. Du rufst eine mutating-Methode auf einer let-Konstante auf. Mach daraus ein var.

Eine Änderung taucht an einer unerwarteten Stelle auf. Du arbeitest mit einer Klasse und hast eine Referenz geteilt. Prüfe, ob ein Struct nicht besser passt.

Übungen

  1. Schreibe ein Struct Warenkorb mit einem privaten Array von Preisen in Cent, einer mutating-Methode hinzufuegen(_ cent: Int) und einer berechneten Eigenschaft summe. Zeige mit einer Kopie, dass zwei Warenkörbe unabhängig sind.
  2. Modelliere den Zustand eines Downloads als Enum mit den Fällen wartend, laeuft(prozent: Int), fertig(dateiname: String) und fehlgeschlagen(grund: String). Schreibe eine Funktion, die für jeden Zustand einen Statustext liefert.
Lösung zu Übung 1
loesung-1.swiftAusgeführt Swift 6.4.0, Linux x86-64, 19.10.2026
struct Warenkorb {
    private var preise: [Int] = []

    mutating func hinzufuegen(_ cent: Int) {
        preise.append(cent)
    }

    var summe: Int { preise.reduce(0, +) }
}

var meiner = Warenkorb()
meiner.hinzufuegen(499)
meiner.hinzufuegen(1_250)

var deiner = meiner          // Kopie
deiner.hinzufuegen(99)

print(meiner.summe, deiner.summe)

Ausgabe

1749 1848
Lösung zu Übung 2
loesung-2.swiftAusgeführt Swift 6.4.0, Linux x86-64, 19.10.2026
enum Download {
    case wartend
    case laeuft(prozent: Int)
    case fertig(dateiname: String)
    case fehlgeschlagen(grund: String)
}

func status(_ d: Download) -> String {
    switch d {
    case .wartend:
        return "Wartet auf Start"
    case .laeuft(let prozent):
        return "Lädt … \(prozent) %"
    case .fertig(let name):
        return "Fertig: \(name)"
    case .fehlgeschlagen(let grund):
        return "Fehler: \(grund)"
    }
}

let verlauf: [Download] = [.wartend, .laeuft(prozent: 40), .fertig(dateiname: "swift.pkg"), .fehlgeschlagen(grund: "keine Verbindung")]
for zustand in verlauf {
    print(status(zustand))
}

Ausgabe

Wartet auf Start
Lädt … 40 %
Fertig: swift.pkg
Fehler: keine Verbindung