Swift ist Apples Programmiersprache, die die Leistungsfähigkeit von Objective-C mit der Sicherheit moderner Sprachen kombiniert. Im Gegensatz zu vielen anderen Sprachen bietet Swift einzigartige Konstrukte: Wertetypen mit automatischer Kopie, Actors mit Compiler-Level-Isolation, Property Wrappers zur Wiederverwendung von Eigenschaftslogik und protokollorientierte Programmierung als Alternative zur klassischen Vererbung. Laut der Apple Swift Dokumentation machen diese Funktionen Code sicherer und leistungsfähiger.
Wichtige Punkte
In Swift ist struct ein Wertetyp, während class ein Referenztyp ist. Dieser grundlegende Unterschied bestimmt das Verhalten bei Zuweisung, Übergabe an Funktionen und Speicherverwaltung. Struct wird bei jeder Zuweisung vollständig kopiert, class wird per Referenz übergeben — nur der Zeiger auf das Heap-Objekt wird kopiert.
Struct implementiert automatisch Wertsemantik — jede Instanz ist unabhängig. Mutation einer struct-Eigenschaft ist nur über var möglich, und Methoden, die Eigenschaften ändern, müssen als mutating gekennzeichnet werden. Struct unterstützt keine Vererbung, kann aber Protokollen entsprechen. Swifts Standardbibliothek verwendet struct für String, Array, Dictionary, Int, Bool — alle fundamentalen Typen sind Wertetypen.
struct User {
let id: Int
var name: String
mutating func updateName(_ newName: String) {
name = newName
}
}
var user1 = User(id: 1, name: "Alice")
var user2 = user1
user2.updateName("Bob")
// user1.name === "Alice", user2.name === "Bob"
// Vollständige Kopie — unabhängige Instanzen
Class wird im Heap gespeichert und unterstützt Referenzgleichheit. Mehrere Variablen können auf dasselbe Objekt verweisen, Änderungen über eine Referenz sind über alle anderen sichtbar. Class unterstützt Vererbung, erlaubt Methodenüberschreibung (override), hat deinit zur Ressourcenbereinigung. In Swift wird class seltener verwendet als struct — Apple empfiehlt, mit struct zu beginnen und nur bei Bedarf an Vererbung oder Referenzsemantik zu class zu wechseln.
class Car {
let model: String
var speed: Int
init(model: String, speed: Int) {
self.model = model
self.speed = speed
}
func accelerate(_ amount: Int) {
speed += amount
}
}
let car1 = Car(model: "Tesla", speed: 0)
let car2 = car1
car2.accelerate(50)
// car1.speed === 50, car2.speed === 50
// Gleiche Referenz — Änderungen überall sichtbar
Die Wahl zwischen struct und class hängt von der Datensemantik ab. Für Konfigurationen, Datenmodelle und Zustände verwenden Sie struct — Kopieren ist sicherer und vorhersagbarer. Für Objekte mit Identität (Benutzersitzung, Datenbankverbindung) und bei Bedarf an Vererbung verwenden Sie class.
Actor ist ein Referenztyp, der seinen veränderlichen Zustand vor gleichzeitigem Zugriff schützt. Der Swift-Compiler garantiert, dass Actor-Variablen nur aus demselben Actor-Kontext zugänglich sind — jeder externe Zugriff verursacht einen Kompilierungsfehler. Dies verhindert Datenrennen auf Sprachebene ohne manuelle Sperren.
Alle Actor-Eigenschaften und -Methoden sind standardmäßig isoliert. Der Aufruf einer Actor-Methode von außen erfordert await — Swift wechselt die Ausführung zum Actor und zurück. Der Actor synchronisiert den Zugriff sequentiell: Anfragen von verschiedenen Tasks werden in eine Warteschlange gestellt. Dies gewährleistet einen konsistenten Zustand innerhalb des Actors. Nicht isolierte Methoden (nonisolated) können ohne await aufgerufen werden, haben aber keinen Zugriff auf veränderliche Eigenschaften.
actor BankAccount {
private var balance: Double
init(initialBalance: Double) {
balance = initialBalance
}
func deposit(amount: Double) {
balance += amount
}
func withdraw(amount: Double) throws {
guard balance >= amount else {
throw BankError.insufficientFunds
}
balance -= amount
}
nonisolated func accountInfo() -> String {
return "Bankkonto"
}
}
let account = BankAccount(initialBalance: 1000)
await account.deposit(amount: 500)
// balance = 1500 — atomar, ohne Wettlaufsituation
Actor ist nützlich für Zustandsverwalter, Caches und Netzwerkdienste, bei denen mehrere Tasks gleichzeitig Daten lesen und schreiben. Im Gegensatz zur manuellen Synchronisation über NSLock oder DispatchQueue wird Actor zur Kompilierzeit überprüft — Fehler sind unmöglich. Isolationsfehler treten zur Kompilierzeit auf, nicht zur Laufzeit.
Property Wrapper ist eine Annotation, die einer Eigenschaft Verhalten hinzufügt, ohne ihre Deklaration zu ändern. SwiftUI und Combine bieten integrierte Wrapper zur Verwaltung des Ansichtszustands. Der Compiler generiert Code, der den Eigenschaftszugriff umschließt und Beobachtungs-, Synchronisations- oder Transformationslogik hinzufügt.
@Published wird in Klassen verwendet, die ObservableObject entsprechen, um Änderungen automatisch zu veröffentlichen. Combine-Abonnenten erhalten bei jeder Aktualisierung Benachrichtigungen. @State ist der lokale Zustand einer SwiftUI-Ansicht. SwiftUI zeichnet die Ansicht automatisch neu, wenn sich @State ändert. @Binding erstellt eine bidirektionale Verbindung zwischen einer Eltern- und Kindansicht, ohne die Daten zu besitzen.
import SwiftUI
import Combine
@main
struct CounterApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
class CounterViewModel: ObservableObject {
@Published var count: Int = 0
func increment() {
count += 1
}
}
struct ContentView: View {
@State private var greeting: String = "Hallo"
@StateObject private var viewModel = CounterViewModel()
var body: some View {
VStack {
CounterDisplay(count: $viewModel.count)
Text(greeting)
Button("Erhöhen", action: viewModel.increment)
}
}
}
struct CounterDisplay: View {
@Binding var count: Int
var body: some View {
Text("Zähler: \(count)")
}
}
Benutzerdefinierte Property Wrapper werden mit @propertyWrapper und einer Struktur mit einem wrappedValue-Feld erstellt. Dies ermöglicht die Wiederverwendung von Logik: Validierung, Normalisierung, UserDefaults-Speicherung. Property Wrapper reduzieren Boilerplate-Code erheblich und machen die Absicht des Entwicklers durch Annotation explizit.
@propertyWrapper
struct Clamped<T: Comparable> {
private var value: T
let range: ClosedRange<T>
init(wrappedValue: T, range: ClosedRange<T>) {
self.range = range
self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
var wrappedValue: T {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
}
struct Settings {
@Clamped(range: 0...100) var volume: Int = 50
}
var s = Settings()
s.volume = 150
// s.volume === 100 — Wert automatisch begrenzt
Generics ist ein mächtiger Mechanismus in Swift zum Schreiben flexiblen Codes ohne Verlust der Typsicherheit. Generische Funktionen und Typen arbeiten mit jedem Typ, der Einschränkungen erfüllt. Protokollorientierte Programmierung (POP) ist ein Swift-Paradigma, bei dem protocol + extension die klassische Klassenvererbung ersetzen und Verhaltenskomposition ermöglichen.
Swift erlaubt es, generische Parameter mit Protokollen einzuschränken (where-Klauseln). Protokoll mit assoziiertem Typ ist das generische Äquivalent für Protokolle: Der konkrete Typ wird zum Zeitpunkt der Konformität bestimmt. Dies ist die Grundlage für typsichere Sammlungen und Algorithmen. Swifts Standardbibliothek verwendet aktiv Generics: Array, Dictionary, Optional — alles generische Typen.
protocol Cacheable {
associatedtype Key: Hashable
associatedtype Value
func get(_ key: Key) -> Value?
mutating func set(_ value: Value, for key: Key)
}
struct MemoryCache<K: Hashable, V>: Cacheable {
private var storage: [K: V] = [:]
typealias Key = K
typealias Value = V
func get(_ key: K) -> V? {
return storage[key]
}
mutating func set(_ value: V, for key: K) {
storage[key] = value
}
}
func firstMatch<T: Equatable>(in array: [T], target: T) -> Int? {
for (index, item) in array.enumerated() {
if item == target {
return index
}
}
return nil
}
let numbers = [10, 20, 30, 40]
let index = firstMatch(in: numbers, target: 30)
// index === 2 — eine generische Methode für alle Equatable-Typen
Protokollerweiterung ist ein Schlüsselmechanismus von POP. In einer Erweiterung können Standardimplementierungen von Protokollmethoden bereitgestellt werden. Typen, die dem Protokoll entsprechen, erhalten diese Implementierung kostenlos und können sie bei Bedarf überschreiben. Dies ermöglicht den Aufbau von Verhaltenshierarchien ohne Klassenvererbung — eine Alternative zur Mehrfachvererbung, die es in Swift nicht gibt.
protocol Drivable {
var speed: Double { get set }
func drive()
}
extension Drivable {
func drive() {
if speed > 0 {
print("Fahre mit \(speed) km/h")
}
}
}
struct Bicycle: Drivable {
var speed: Double = 15
}
struct Plane: Drivable {
var speed: Double = 900
func drive() {
print("Fliege mit \(speed) km/h")
}
}
let bike = Bicycle()
bike.drive() // "Fahre mit 15.0 km/h" — Standard
let plane = Plane()
plane.drive() // "Fliege mit 900.0 km/h" — überschrieben
POP mit Erweiterung ermöglicht Komposition durch mehrere Protokolle — ein Typ kann mehreren Protokollen entsprechen und deren Implementierungen erhalten. Dies ist flexibler als die Einfachvererbung von Klassen, bei der die gesamte Hierarchie festgelegt ist. Swifts Standardbibliothek ist auf POP aufgebaut: CustomStringConvertible, Equatable, Hashable, Codable — alles Protokolle mit Erweiterungen.
Enum ist in Swift ein deutlich mächtigeres Konstrukt als in anderen Sprachen. Jeder Fall kann zugehörige Werte haben — einen beliebigen Satz von Daten beliebigen Typs. In Kombination mit Extension und Pattern Matching wird Enum zur Grundlage für die Modellierung von Zuständen, Operationsergebnissen und endlichen Zustandsmaschinen.
Assoziierte Werte erlauben es jedem Fall, eindeutige Daten zu speichern. Zum Beispiel enthält der Fall loading keine Daten, success enthält das Ergebnis, failure enthält einen Fehler. Pattern Matching über Switch extrahiert die zugehörigen Werte und behandelt jeden Fall. Der Compiler prüft auf Vollständigkeit (exhaustive switch) — dies eliminiert vergessene Zustände.
enum NetworkResult<T> {
case loading
case success(T)
case failure(Error)
}
enum MediaFile {
case image(url: URL, width: Int, height: Int)
case video(url: URL, duration: Double)
case audio(url: URL, bitrate: Int)
}
func handle(_ result: NetworkResult<String>) {
switch result {
case .loading:
print("Laden...")
case .success(let data):
print("Erhalten: \(data)")
case .failure(let error):
print("Fehler: \(error.localizedDescription)")
}
}
let file = MediaFile.video(url: URL(string: "https://example.com/video.mp4")!, duration: 120)
if case .video(let url, let duration) = file {
print("Video \(url) Dauer \(duration)s")
}
Swift erlaubt das Hinzufügen von Methoden, berechneten Eigenschaften und Protokollkonformität über Extension für Enum. Dies erfordert keine Änderung der ursprünglichen Deklaration — der Code bleibt sauber und modular. Methoden in Extension können mit assoziierten Werten arbeiten, abgeleitete Daten berechnen und Geschäftslogik implementieren.
extension MediaFile {
var fileName: String {
switch self {
case .image(let url, _, _),
.video(let url, _),
.audio(let url, _):
return url.lastPathComponent
}
}
var description: String {
switch self {
case .image(_, let w, let h):
return "Bild \(w)x\(h)"
case .video(_, let d):
return "Video \(d)s"
case .audio(_, let b):
return "Audio \(b) kbps"
}
}
}
extension MediaFile: Equatable {
static func == (lhs: MediaFile, rhs: MediaFile) -> Bool {
return lhs.fileName == rhs.fileName
}
}
let files: [MediaFile] = [file]
files.forEach { print($0.description) }
// "Video 120.0s"
Enum mit zugehörigen Werten + Extension ist der Ersatz für versiegelte Klassen aus Kotlin oder diskriminierte Unionen aus anderen Sprachen. Swift verwendet diese Kombination für Result (ein Standardtyp in der Standardbibliothek), Optional (Optional ist ein Enum mit .none und .some) und die Zustandsbehandlung in SwiftUI. Pattern Matching garantiert, dass alle Zustände behandelt werden — Laufzeitfehler sind unmöglich.
Häufig gestellte Fragen
Struct — Wertetyp, wird bei Zuweisung kopiert, unterstützt keine Vererbung. Class — Referenztyp, wird per Referenz übergeben, unterstützt Vererbung, deinit und Identitätsprüfung (===). Apple empfiehlt, mit struct zu beginnen und nur bei Bedarf an Referenzsemantik zu class zu wechseln.
Actor — threadsicherer Referenztyp. Der Compiler verhindert gleichzeitigen Zugriff auf veränderlichen Zustand aus verschiedenen Kontexten. Verwenden Sie Actor für Zustandsverwalter, Caches und Dienste, auf die mehrere Tasks zugreifen. Der Aufruf von Actor-Methoden erfordert await.
Wichtigste: @State — lokaler Ansichtszustand; @Binding — Datenverbindung ohne Besitz; @Published — Veröffentlichung von Änderungen in ObservableObject; @StateObject — Besitz von ObservableObject; @EnvironmentObject — Abhängigkeit aus der Umgebung. Benutzerdefinierte Wrapper werden über @propertyWrapper erstellt.
POP — ein Paradigma, bei dem protocol + extension das Verhalten anstelle der Klassenvererbung definieren. Ein Typ entspricht mehreren Protokollen und erhält deren Implementierungen kostenlos. Dies ist Komposition statt Vererbung — flexibler und sicherer. Swifts Standardbibliothek (Equatable, Codable) ist auf POP aufgebaut.
Enum in Swift unterstützt assoziierte Werte — jeder Fall kann beliebige Daten verschiedener Typen speichern. In Kombination mit Pattern Matching (Switch) bietet dies ein mächtiges Modell für Zustände, Fehler und Domain-Driven Design. Optional und Result sind Standard-Swift-Enums mit assoziierten Werten.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.