Lektion 1.10 · Stufe 1 – Swift-Fundament

Funktionen

ca. 15 Minuten Lesezeit

Lernziel

Nach dieser Lektion kannst du eigene Funktionen schreiben, ihnen Werte übergeben (Parameter), Ergebnisse zurückgeben und Parameter mit Argument-Labels so benennen, dass sich der Aufruf wie ein Satz liest.

Das Problem: Derselbe Code an vielen Stellen

Stell dir vor, du berechnest in einem Programm an mehreren Stellen den Preis inklusive Mehrwertsteuer:

Swift
let shirt = 20.0
print(shirt * 1.2)

let shoes = 80.0
print(shoes * 1.2)

let hat = 15.0
print(hat * 1.2)

Jetzt ändert sich der Steuersatz. Du musst jede einzelne Stelle finden und anpassen. Übersiehst du eine, rechnet dein Programm falsch, und niemand merkt es.

Die Lösung: Den Code einmal schreiben, ihm einen Namen geben und ihn überall aufrufen. Das ist eine Funktion.

Die einfachste Funktion

Swift
func sayHello() {
    print("Hallo!")
}

sayHello()   // Hallo!
sayHello()   // Hallo!
  • func leitet eine Funktion ein.
  • sayHello ist ihr Name, wieder in camelCase. Funktionsnamen beschreiben meist eine Tätigkeit, deshalb beginnen sie oft mit einem Verb.
  • Die runden Klammern () sind Pflicht, auch wenn sie leer sind.
  • Der Block in geschweiften Klammern ist der Inhalt der Funktion, der Funktionskörper.

Das Anlegen nennt man Deklarieren oder Definieren. Dabei passiert noch nichts. Erst der Aufruf sayHello() führt den Code aus.

print ist übrigens auch eine Funktion, nur dass Apple sie schon für dich geschrieben hat.

Parameter: Werte übergeben

Eine Funktion, die immer dasselbe tut, ist begrenzt nützlich. Mit Parametern gibst du ihr Werte mit:

Swift
func greet(name: String) {
    print("Hallo, \(name)!")
}

greet(name: "Anna")   // Hallo, Anna!
greet(name: "Ben")    // Hallo, Ben!

In den Klammern steht der Parameter: ein Name (name) und ein Typ (String). Innerhalb der Funktion kannst du name wie eine Konstante benutzen.

Beim Aufruf schreibst du den Parameternamen mit Doppelpunkt davor: greet(name: "Anna"). Das macht Aufrufe sehr gut lesbar.

Mehrere Parameter

Swift
func greet(name: String, times: Int) {
    for _ in 1...times {
        print("Hallo, \(name)!")
    }
}

greet(name: "Anna", times: 3)

Die Reihenfolge beim Aufruf muss der Reihenfolge in der Definition entsprechen.

Rückgabewerte: Ergebnisse zurückgeben

Bisher haben unsere Funktionen etwas ausgegeben. Viel häufiger sollen sie aber etwas berechnen und das Ergebnis zurückgeben:

Swift
func priceWithTax(net: Double) -> Double {
    return net * 1.2
}

let shirtPrice = priceWithTax(net: 20)
print(shirtPrice)   // 24.0
  • -> Double nach den Klammern heißt: „Diese Funktion gibt einen Double zurück.“ Den Pfeil tippst du als Bindestrich und Größer-Zeichen.
  • return beendet die Funktion und liefert den Wert zurück an die Stelle des Aufrufs.

Das Ergebnis kannst du in einer Konstante speichern, direkt ausgeben oder weiterverwenden:

Swift
let total = priceWithTax(net: 20) + priceWithTax(net: 80)

Und jetzt der eigentliche Gewinn: Ändert sich der Steuersatz, änderst du eine einzige Zeile.

Implizites return

Besteht der Funktionskörper nur aus einem einzigen Ausdruck, kannst du return weglassen:

Swift
func priceWithTax(net: Double) -> Double {
    net * 1.2
}

Diese Kurzform wirst du in SwiftUI überall sehen, zum Beispiel bei var body: some View { ... }. Auch dort steht kein return.

Tipp

Eine Funktion mit -> muss auf jedem Weg etwas zurückgeben. Vergisst du in einem if-Zweig das return, meldet Xcode Missing return in global function expected to return 'Double'.

Argument-Labels: Aufrufe wie Sätze

Swift legt großen Wert darauf, dass sich ein Funktionsaufruf wie ein englischer Satz liest. Dafür kann ein Parameter zwei Namen haben:

Swift
func greet(to name: String) {
    print("Hallo, \(name)!")
}

greet(to: "Anna")
  • to ist das Argument-Label. Es wird beim Aufruf verwendet.
  • name ist der Parametername. Er wird innerhalb der Funktion verwendet.

So liest sich der Aufruf als „greet to Anna“, und innerhalb der Funktion hast du trotzdem einen sinnvollen Namen.

Kein Label mit _

Manchmal ist schon aus dem Funktionsnamen klar, was übergeben wird. Dann lässt du das Label mit einem Unterstrich weg:

Swift
func square(_ number: Int) -> Int {
    number * number
}

print(square(5))   // 25

square(5) liest sich natürlicher als square(number: 5). print("Hallo") ist übrigens genauso aufgebaut, deshalb steht dort kein Label.

Gute Namen für Funktionen

Lies den Aufruf laut vor. Klingt er wie ein verständlicher Satz?

  • ✅ move(from: a, to: b)
  • ✅ isValid(email)
  • ❌ doIt(x: a, y: b)

Standardwerte

Parameter können einen Standardwert haben. Dann darf man sie beim Aufruf weglassen:

Swift
func priceWithTax(net: Double, rate: Double = 0.2) -> Double {
    net * (1 + rate)
}

priceWithTax(net: 100)              // 120.0, Standardsatz
priceWithTax(net: 100, rate: 0.1)   // 110.0, ermäßigter Satz

Das wirst du in SwiftUI ständig sehen. VStack(spacing: 20) funktioniert genauso: spacing hat einen Standardwert, du kannst ihn angeben oder weglassen.

Jetzt mit guard

In der letzten Lektion hast du guard let kennengelernt. Jetzt kannst du es richtig einsetzen:

Swift
func double(_ text: String) -> Int? {
    guard let number = Int(text) else {
        return nil
    }
    return number * 2
}

print(double("21"))     // Optional(42)
print(double("Hallo"))  // nil

Diese Funktion gibt ein Int? zurück, weil sie nicht immer ein Ergebnis hat. Genau so entstehen die Optionals, die du aus Int(...) und first kennst.

Lokal ist lokal

Variablen, die du in einer Funktion anlegst, existieren nur dort:

Swift
func calculate() {
    let result = 42
}

calculate()
print(result)

Fehlermeldung

Cannot find 'result' in scope

Das ist ein Vorteil: Jede Funktion ist ein abgeschlossener Bereich. Was darin passiert, beeinflusst nicht versehentlich den Rest deines Codes. Willst du ein Ergebnis nach außen geben, nutzt du return.

Typische Fehler

Label vergessen oder falsch

Swift
func greet(name: String) { ... }
greet("Anna")

Fehlermeldung

Missing argument label 'name:' in call

Beim Aufruf muss das Label stehen: greet(name: "Anna"). Oder du definierst den Parameter mit _.

Rückgabewert nicht benutzt

Swift
priceWithTax(net: 20)

Achtung

Result of call to 'priceWithTax(net:)' is unused

Die Funktion berechnet etwas, aber du machst nichts damit. Meist hast du vergessen, das Ergebnis zu speichern oder auszugeben.

Parameter ändern

Swift
func addOne(number: Int) {
    number += 1
}

Fehlermeldung

Left side of mutating operator isn't mutable: 'number' is a 'let' constant

Parameter sind innerhalb der Funktion Konstanten. Leg eine neue Variable an (var result = number + 1) und gib sie zurück.

Falscher Rückgabetyp

Swift
func half(_ number: Int) -> Int {
    return Double(number) / 2
}

Fehlermeldung

Cannot convert return expression of type 'Double' to return type 'Int'

Was du zurückgibst, muss zum Typ nach -> passen.

FAQ

Anfänger

Was bedeutet das überhaupt?

Wann lohnt sich eine eigene Funktion? Sobald du Code zum zweiten Mal schreibst, oder wenn ein Abschnitt eine klar benennbare Aufgabe erfüllt. Eine Funktion mit gutem Namen wie calculateShippingCost erklärt den Code besser als jeder Kommentar.

Wie lang darf eine Funktion sein? Es gibt keine feste Grenze. Als Faustregel: Passt sie nicht mehr auf den Bildschirm oder brauchst du das Wort „und“, um zu beschreiben, was sie tut, teil sie auf.

Wo muss die Funktion stehen, vor oder nach dem Aufruf? Im Playground muss sie vor dem Aufruf stehen, weil er von oben nach unten läuft. In normalen Projektdateien ist die Reihenfolge egal.

Fortgeschritten

Warum verhält es sich so?

Kann eine Funktion mehrere Werte zurückgeben? Ja, mit einem Tupel:

Swift
func minMax(_ numbers: [Int]) -> (min: Int, max: Int)? {
    guard let min = numbers.min(), let max = numbers.max() else {
        return nil
    }
    return (min, max)
}

if let result = minMax([3, 9, 1]) {
    print(result.min, result.max)   // 1 9
}

Für mehr als zwei oder drei Werte ist eine eigene Struct (Lektion 1.12) meist übersichtlicher.

Kann eine Funktion einen Parameter verändern, sodass es außen ankommt? Ja, mit inout: func addOne(_ number: inout Int) { number += 1 }, aufgerufen mit addOne(&value). In App-Code brauchst du das selten. Ein Rückgabewert ist fast immer klarer.

Dürfen zwei Funktionen gleich heißen? Ja, wenn sich ihre Parameter unterscheiden: greet(name:) und greet(name:times:) sind für Swift zwei verschiedene Funktionen. Das nennt man Überladen.

Profi

Was passiert unter der Haube?

Wie lautet der „vollständige Name“ einer Funktion? Swift bezeichnet Funktionen mit ihren Labels: greet(name:times:) oder square(_:). So steht es auch in Fehlermeldungen und in Apples Dokumentation. Wenn du dort insert(_:at:) liest, weißt du sofort: erster Parameter ohne Label, zweiter mit at:.

Was bedeutet @discardableResult? Ein Attribut, das die Warnung „Result … is unused“ unterdrückt, wenn das Ergebnis einer Funktion nur gelegentlich interessant ist. removeLast() bei Arrays ist so markiert: Es gibt das entfernte Element zurück, aber du musst es nicht verwenden.

Übungen

Anfänger

Übung 1: Flächenrechner

Schreib eine Funktion area(width:height:), die die Fläche eines Rechtecks zurückgibt. Teste sie mit zwei verschiedenen Rechtecken.

Lösung Übung 1Erst selbst probieren!
Swift
func area(width: Double, height: Double) -> Double {
    width * height
}

print(area(width: 4, height: 5))     // 20.0
print(area(width: 2.5, height: 3))   // 7.5
Fortgeschritten

Übung 2: Ist gerade?

Schreib eine Funktion isEven(_:), die für eine Zahl true oder false zurückgibt. Nutze sie dann in einer Schleife, um alle geraden Zahlen von 1 bis 20 auszugeben.

Lösung Übung 2Erst selbst probieren!
Swift
func isEven(_ number: Int) -> Bool {
    number % 2 == 0
}

for number in 1...20 {
    if isEven(number) {
        print(number)
    }
}

if isEven(number) liest sich fast wie ein Satz. Genau das ist der Gewinn guter Funktionsnamen.

Profi

Übung 3: Passwort-Prüfer

Schreib eine Funktion passwordProblem(_:), die für ein Passwort nil zurückgibt, wenn es in Ordnung ist, und sonst einen Text mit dem ersten gefundenen Problem: kürzer als 8 Zeichen, keine Ziffer, oder enthält das Wort „passwort“ (egal ob groß oder klein geschrieben). Verwende guard.

Tipp: text.contains { $0.isNumber } prüft, ob eine Ziffer enthalten ist. Die Schreibweise mit $0 erklären wir in der nächsten Lektion.

Lösung Übung 3Erst selbst probieren!
Swift
func passwordProblem(_ password: String) -> String? {
    guard password.count >= 8 else {
        return "Mindestens 8 Zeichen nötig."
    }
    guard password.contains(where: { $0.isNumber }) else {
        return "Mindestens eine Ziffer nötig."
    }
    guard !password.lowercased().contains("passwort") else {
        return "Bitte nicht das Wort „passwort“ verwenden."
    }
    return nil
}

let checks = ["abc", "langespasswort", "Passwort123", "Sonnen4Blume"]
for check in checks {
    print("\(check): \(passwordProblem(check) ?? "ok ✅")")
}

Die Funktion kombiniert fast alles aus Stufe 1 bisher: Strings, Bedingungen, Optionals, guard und ??.

Zusammenfassung

  • func name() { ... } definiert eine Funktion, name() ruft sie auf.
  • Parameter übergeben Werte: func greet(name: String).
  • -> Typ und return geben ein Ergebnis zurück. Bei einem einzigen Ausdruck darf return wegfallen.
  • Argument-Labels machen Aufrufe lesbar: greet(to name: String) → greet(to: "Anna").
  • _ lässt das Label weg: square(5).
  • Standardwerte machen Parameter optional beim Aufruf.
  • Variablen in einer Funktion sind lokal.

Spickzettel

Swift
func sayHello() { ... }
func greet(name: String) { ... }              // greet(name: "A")
func greet(to name: String) { ... }           // greet(to: "A")
func square(_ n: Int) -> Int { n * n }        // square(5)
func price(net: Double, rate: Double = 0.2) -> Double { ... }
func parse(_ s: String) -> Int? {
    guard let n = Int(s) else { return nil }
    return n
}

Lektion geschafft?

Markiere sie als erledigt, damit du deinen Fortschritt siehst.