Werte- und Referenztypen definieren - struct & class

von @ralfebert · aktualisiert am 4. November 2021
Xcode 13 & Swift 5.5
Swift Kurzreferenz

Das Typsystem von Swift unterscheidet zwischen Wertetypen, die mit struct deklariert werden und Referenztypen, die mit class deklariert werden. Die beiden Arten von Typen unterscheiden sich wesentlich in der Handhabung.

Wertetypen mit struct definieren

In Swift gibt es mit struct die Möglichkeit, eigene Wertetypen zu definieren. Diese kommen immer dann zum Einsatz, wenn es darum geht, Daten strukturiert zu repräsentieren.

Beispielsweise könnte man sich einen eigenen Datentyp definieren, um Geldbeträge abzubilden:

struct MoneyAmount {
}

Eine „Variablen bzw. Konstantendeklaration“ in einer Typdefinition wird automatisch zu einer von außerhalb sichtbaren Eigenschaft des Typs:

import Foundation

struct MoneyAmount {
    var amount: Decimal
    var currency: String
}

Mit struct definierte Wertetypen haben ein pass-by-value-Verhalten, d.h. bei jeder Übergabe oder Zuweisung werden die enthaltenen Werte selbst übergeben:

var amount1 = MoneyAmount(amount: 50, currency: "€")
var amount2 = amount1

amount2.amount = 30

Initializer

Für die Übergabe der initialen Werte der deklarierten Eigenschaften definiert der Swift-Compiler automatisch einen Initializer. Es können eigene Initializer deklariert werden - dadurch entfällt jedoch dieser standardmäßig deklarierte Initializer (Tipp: Refactor » Generate Memberwise Initializer):

struct MoneyAmount {
    var amount: Decimal
    var currency: String

    init(currency: String) {
        self.amount = 0
        self.currency = currency
    }

    init(amount: Decimal, currency: String) {
        self.amount = amount
        self.currency = currency
    }
}

Methoden

Wird eine Funktion innerhalb einer Typdeklaration definiert, wird diese zu einer Methode. Mit dem Schlüsselwort self wird auf Eigenschaften und Methoden der Klasse zugegriffen (self kann entfallen, sofern keine gleichnamige lokale Variable definiert ist).

Bei der Definition von Methoden für Wertetypen ist zu beachten, dass nur als mutating deklarierte Methoden Eigenschaften verändern dürfen. Auf diesem Weg stellt der Compiler sicher, dass mit let konstant deklarierte Werte nicht verändert werden:

struct MoneyAmount {
    var amount: Decimal
    var currency: String

    func increment() {
        self.amount += 1
    }
}

Oft werden daher für Wertetypen sowohl Methoden angeboten, die den Wert direkt verändern, als auch Methoden, die einen neuen, veränderten Wert zurückliefern. Beispielsweise könnte der MoneyAmount-Typ Methoden für die Rundung anbieten. Den Swift-Namenskonventionen folgend würde round den Wert unmittelbar verändern und rounded einen neuen, gerundeten Wert zurückliefern:

struct MoneyAmount {
    var amount: Decimal
    var currency: String

    mutating func round(digits: Int = 2) {
        var result = self.amount
        NSDecimalRound(&result, &self.amount, digits, .bankers)
        self.amount = result
    }

    func rounded(digits: Int = 2) -> MoneyAmount {
        var newValue = self
        newValue.round(digits: digits)
        return newValue
    }
}

Referenztypen mit class definieren

Für eine Klasse wird durch den Aufruf des Initializers Speicherplatz für das Objekt allokiert und eine Referenz auf dieses Objekt zurückgegeben. Wird also ein Objekt übergeben, wird lediglich die Referenz auf das Objekt übergeben.

Dies könnte in einer App etwa sinnvoll sein für ein CurrencyConverter-Objekt, welches Wechselkurse aus dem Internet abruft und die Konvertierung von MoneyAmount-Beträgen anbietet - einmal erzeugt, soll dies in der Regel von mehreren Stellen referenziert und verwendet werden:

struct ExchangeRate {
    var fromCurrency: String
    var toCurrency: String
    var rate: Decimal
}

class CurrencyConverter {
    var exchangeRates = [ExchangeRate]()

    func convert(amount: MoneyAmount, to currency: String) -> MoneyAmount {
        // ...
    }
}

Wird ein Objekt einer mit class definierten Klasse übergeben wird immer eine Referenz übergeben:

var converter1 = CurrencyConverter()
var converter2 = converter1

Bei der Verwendung eines solchen Typs muss unbedingt auf diesen referenzierende Verhalten geachtet werden.

Weitere Informationen