zurück zum Artikel

Moderne Programmierung mit Swift, Teil 1

Thomas Sillmann, Julia Schmidt
Moderne Programmierung mit Swift, Teil 1

Knapp drei Jahre nach der ersten Vorstellung auf Apples Entwicklerkonferenz WWDC 2014 hat die Programmiersprache Swift bereits zwei Major Releases zu verzeichnen. Eine dreiteilige Artikelserie widmet sich dem Status quo und stellt gleichermaßen grundlegende wie neu eingeführte Konzepte vor.

Swift hat in den letzten Jahren einen starken Wandel durchlebt, der erst in jüngster Zeit langsam abflaut. Einige Konzepte wurden gestrichen und dafür komplett neue hinzugefügt. Apple und die Open-Source-Community von Swift sind aktuell primär damit beschäftigt, die Basis der Sprache sowie Typen und Funktionen der Standard Library zu vereinheitlichen und zu optimieren, was sich insbesondere in der für Herbst erwarteten Version 4 der Sprache niederschlagen wird.

Um Einsteigern und mit Swift programmierenden Entwicklern einen Überblick über zentrale und neue Konzepte der Sprache an die Hand zu geben, stellt eine dreiteilige Artikelserie nach und nach Elemente wie Error Handling, Initialisierung, Operator Methods und das Zusammenspiel zwischen Swift und Objective-C im Detail vor. Den Anfang machen Error Handling, die Speicherverwaltung in Swift und Option Sets.

Error Handling gehörte zu den spannendsten Features, die Swift mit der 2015 freigegebenen Version 2 zuteil wurden. Es stellt eine Alternative zum Einsatz der NSError-Klasse aus dem Foundation Kit dar, die in Objective-C heute noch dazu dient, Fehler abzufangen und auf sie zu reagieren. Da Error Handling darüber hinaus fester Bestandteil der Sprache ist, ist dessen Einsatz nicht wie NSError auf Apple-Plattformen beschränkt und lässt sich unter Linux und auf dem Server verwenden.

Basis des Error Handling in Swift ist das Schlüsselwort throws. Entwickler können Methoden damit versehen, um anzugeben, dass sie möglicherweise fehlschlagen und einen Fehler zurückgeben können. throws wird dabei immer hinter den runden Klammern gesetzt, die die Parameter einer Methode enthalten. Damit steht das Schlüsselwort immer vor den geschweiften Klammern, die die Implementierung einer Methode enthalten. Bei Methoden mit einem Rückgabewert ist es vor der Angabe des entsprechenden Rückgabetyps zu finden:

func functionWithThrowingError() throws {
// Implementierung der Funktion
}

func functionWithThrowingErrorAndReturnValue() throws -> Int {
// Implementierung der Funktion
}

Nur wenn eine Methode mit dem Schlüsselwort throws deklariert ist, ist sie auch imstande, einen Fehler zurückzugeben. Grundlage für dessen Definition ist das Protokoll Error. Fehler, die in einer Anwendung entstehen können, sind mithilfe eines protokollkonformen Typs abzubilden. Typischerweise setzt man zu dem Zweck Enumerations ein und definiert für jeden potenziellen Fehler einen eigenen Case. Sollte einer zur Beschreibung nicht ausreichen, lässt sich optional noch mit Associated Values arbeiten, die einem Case zugeordnet sind.

Ein Beispiel für die Abbildung von Fehlern zeigt der folgende Quelltextauszug. Darin wird eine Enumeration DrivingError erstellt, die zwei Fehlerfälle festlegt: einen, falls für ein Fahrzeug kein Treibstoff zur Verfügung steht, und einen für den Fall, dass die Geschwindigkeit des Fahrzeugs zu hoch ist. Der letzte Case verfügt zusätzlich über einen Associated Value, der über die Höchstgeschwindigkeit eines Fahrzeugs informiert.

enum DrivingError: Error {
case noFuel
case speedTooHigh(maximumSpeed: UInt)
}

Innerhalb einer Methode wird ein Fehler mithilfe des Schlüsselworts throw zurückgegeben, wobei direkt nach letzterem der entsprechende Fehler aus einem zum Error-Protokoll konformen Typ angegeben wird. Dabei gilt, dass throw nur dann innerhalb einer Methode gefeuert werden darf, wenn die Methode selbst durch das Schlüsselwort deklariert wurde.

Der folgende Quellcodeauszug zeigt ein Beispiel für das Feuern von Fehlern mit dem Error-Handling-Modell von Swift:

struct Car {
var hasFuel: Bool
let maximumSpeed: UInt
var currentSpeed: UInt

func startDriving() throws {
guard hasFuel else {
throw DrivingError.noFuel
}

print("Start driving...")
}

mutating func increaseCurrentSpeedWithSpeed(_ speed: UInt) throws -> UInt {

let updatedSpeed = currentSpeed + speed
guard updatedSpeed <= maximumSpeed else {

throw DrivingError.speedTooHigh(maximumSpeed: maximumSpeed)
}

currentSpeed = updatedSpeed
print("Current speed is now \(currentSpeed).")
return currentSpeed
}
}

Das Beispiel deklariert eine Structure Car, die über insgesamt drei Properties verfügt:

Darüber hinaus kommen noch zwei Methoden zum Einsatz, die beide via throws deklariert sind und so unter bestimmten Umständen wenigstens einen Fehler zurückgeben können. Die Methode startDriving() prüft den Wert der Eigenschaft hasFuel. Ist er false, wird der Fehler noFuel der DrivingError-Enumeration zurückgegeben und die Methode verlassen, andernfalls gibt das Programm eine Info auf der Konsole aus, die besagt, dass das Auto losfährt. Die zweite Methode, increaseCurrentSpeedWithSpeed(_:) erhöht die aktuelle Geschwindigkeit um den übergebenen Wert und prüft anschließend, ob damit möglicherweise die Maximalgeschwindigkeit des Fahrzeugs überschritten ist. Falls ja, gibt das Programm den Fehler speedTooHigh mitsamt der entsprechenden Höchstgeschwindigkeit zurück. Andernfalls wird der Wert der Property currentSpeed geändert und zusätzlich eine Meldung auf der Konsole ausgegeben. Anschließend liefert die Structure die neue aktuelle Geschwindigkeit zurück.

Es stellt sich als Nächstes die Frage, wie sich ein Fehler beim Aufruf einer solchen Methode abfangen und auswerten lässt. Swift bietet dazu unterschiedliche Optionen und Vorgehensweisen. Die beste und effizienteste Art, einen Fehler in der Sprache abzufangen und auszuwerten, besteht in der Verwendung eines sogenannten do-catch-Blocks. Er wurde zusammen mit dem Error Handling in Swift eingeführt und dient ausschließlich dazu, auf einen durch eine Methode ausgelösten Fehler zu reagieren. Zu dem Zweck besteht ein solcher Block aus mehreren Bestandteilen.

Zunächst ist da der do-Abschnitt. Darin führt das Programm typischerweise wenigstens eine Methode aus, die mittels throws deklariert ist und damit einen Fehler feuern kann (ohne den Aufruf einer solchen Methode macht die Verwendung von do-catch in Swift auch keinen Sinn). Außerdem enthält do noch alle weiteren Befehle, die ausgeführt werden sollen, falls Fehler ausbleiben.

Danach folgen ein oder mehrere sogenannte catch-Abschnitte. Jeder davon kann explizit auf einen spezifischen Fehler prüfen, der sich durch die in do aufgerufenen Methode auslösen lässt. Kommt es bei der Ausführung jener Methoden zu eben diesem Fehler, werden der do-Abschnitt verlassen und der catch-Block aufgerufen, der auf den Fehler prüft.

Es lässt sich zudem ein catch-Block ohne zugewiesenen und zu prüfenden Fehler erstellen. Er fungiert als Joker und kommt zum Einsatz, wenn alle vorangegangenen mittels catch explizit geprüften Fehler nicht aufgetreten sind und folglich ein nicht definierter vorliegt.

Zum besseren Verständnis zeigt der folgende Codeauszug den typischen Aufbau eines do-catch-Blocks und in welchen Abschnitten welcher Code untergebracht ist:

do {
// Aufruf einer Funktion, die möglicherweise einen Fehler feuert
// Implementierung der Befehle für den Fall, dass kein Fehler auftritt

} catch <ZU PRÜFENDER FEHLER> {

// Auszuführende Befehle, falls der zu prüfende Fehler auftritt

} catch <ANDERER ZU PRÜFENDER FEHLER> {

// Auszuführende Befehle, falls der andere zu prüfende Fehler auftritt

} catch {

// Auszuführende Befehle, falls ein anderer als die zuvor geprüften
// Fehler auftritt

}

Wichtig ist dabei, alle Methoden, die möglicherweise einen Fehler zurückgeben, mit dem vorangestellten Schlüsselwort try aufzurufen.

Wie das Ganze in der Praxis aussehen kann, zeigt das nächste Beispiel. Dort wird eine Instanz der zuvor deklarierten Structure Car erstellt und anschließend darüber in einem do-catch-Block die Instanzmethode startDriving() aufgerufen. Da die Property hasFuel jener Instanz auf false gesetzt ist, kommt es zum Fehler noFuel der Enumeration DrivingError, weshalb das Programm nach Aufruf jener Methode den catch-Block des Fehlers ausführt.

var myCar = Car(hasFuel: false, maximumSpeed: 200, currentSpeed: 0)

do {
try myCar.startDriving()
print("Auto fährt los...")
} catch DrivingError.noFuel {

print("Es fehlt Treibstoff zum Losfahren...")
}

// Es fehlt Treibstoff zum Losfahren...

Da kein Fehler eintritt, führt das Programm den Code nach der fehlgeschlagenen Methode startDriving() innerhalb des do-Abschnitts nicht aus, weshalb auf der Konsole nicht die Meldung "Auto fährt los" erscheint. Stattdessen wird dort "Es fehlt Treibstoff zum Losfahren" angezeigt, wofür der Code verantwortlich ist, den der zurückgegebene Fehler ausgeführt hat.

Nach dem Schema lassen sich mittels Error Handling alle Methoden ausführen, die aufgrund ihrer throws-Deklaration einen Fehler zurückgeben, den Entwickler abfangen und verarbeiten können. Es gibt allerdings noch weitere Optionen, mit derartigen Methoden umzugehen und sie auszuführen. Gibt eine Methode außer dem möglichen Fehler einen Wert zurück, gibt es die Gelegenheit, die Methode wie gewohnt auszuführen und den Rückgabewert als sogenanntes Optional zu erhalten. Dazu ist die Methode mit dem Schlüsselwort try? (man beachte das Fragezeichen) aufzurufen. Auf die Art und Weise liefert der Aufruf der Methode entweder – bei erfolgreichem Durchlauf – ihren Ergebniswert zurück oder nil, falls ein Fehler gefeuert wird. Entwickler erhalten beim Auftreten einer Fehlfunktion folglich kein Feedback darüber, um welchen Fehler es sich genau handelt, können damit aber Methoden mit try ausführen, ohne das aufwendigere Konstrukt eines do-catch-Blocks zu implementieren.

Ein Beispiel für die Verwendung von try? zum Aufruf einer Methode zeigt der folgende Codeausschnitt. Dort wird eine Variable namens myCarCurrentSpeed deklariert und ihr das Ergebnis des Aufrufs der Instanzmethode increaseCurrentSpeedWithSpeed(_:) über die zuvor erstellte myCar-Instanz zugewiesen. Da jene Methode mittels throws deklariert ist und so einen Fehler zurückgeben kann, ist sie mit try aufzurufen. Weil für das Prüfen und Abfangen des Fehlers aber kein do-catch-Block zum Einsatz kommt, können Entwickler stattdessen das Schlüsselwort try? verwenden und so den Rückgabewert der Methode in ein Optional umwandeln. Kommt es beim Ausführen der Methode zu einem Fehler, wird nil zurückgegeben.

var myCarCurrentSpeed = try? myCar.increaseCurrentSpeedWithSpeed(80)

// myCarCurrentSpeed entspricht 80

myCarCurrentSpeed = try? myCar.increaseCurrentSpeedWithSpeed(120)

// myCarCurrentSpeed entspricht 120

myCarCurrentSpeed = try? myCar.increaseCurrentSpeedWithSpeed(30)

// myCarCurrentSpeed entspricht nil

Dabei ist ein wichtiges Detail zu beachten: Beim Rückgabewert einer Methode, die mit try? aufgerufen wird, handelt es sich immer um ein Optional, selbst wenn der deklarierte Typ des Rückgabewerts nicht als solches definiert ist. Im gezeigten Beispiel entspricht die Variable myCarCurrentSpeed somit nicht dem Typ UInt (bei dem es sich um den Rückgabetyp der Methode increaseCurrentSpeedWithSpeed(_:) handelt), sondern UInt?.

Alternativ zu try? lassen sich Methoden durch das Schlüsselwort try! (man beachte das Ausrufezeichen) aufrufen. In dem Fall wird die Methode direkt ausgeführt und jeglicher Fehler ignoriert. Mögliche Rückgabewerte wandelt das Programm dabei nicht in ein Optional um, sie entsprechen daher exakt dem Typ, der in der jeweiligen Methode definiert ist.

Der Einsatz von try! ist allerdings mit Vorsicht zu genießen: Kommt es beim Ausführen der aufgerufenen Methode zu einem Fehler, stürzt die Anwendung ab. Entsprechend sollten Entwickler eine Methode immer nur dann so aufrufen, wenn sie sicher sind, dass sie in der jeweiligen Situation auch erfolgreich durchlaufen wird.

Swift nimmt Entwicklern einen Großteil der Arbeit ab, wenn es um die Speicherverwaltung geht. Per ARC (Automatic Reference Counting) werden – genau wie in Objective-C – die Referenzen auf eine Instanz im Speicher gezählt. Ist die Zahl größer 0, heißt das, dass die Instanz noch benötigt und somit auch im Speicher gehalten wird. Entspricht sie hingegen 0, ist das das Zeichen dafür, dass das Programm die Instanz nicht länger benötigt und aus dem Speicher entfernen kann.

Allerdings kann es bei der Technik zu sogenannten Strong Reference Cycles kommen. Sie entstehen, wenn zwei Instanzen sich gegenseitig referenzieren, während das Programm die Variablen freigibt, die je einen Verweis auf jene Instanzen besitzen. Dadurch gibt es keinen Weg mehr, um auf die beiden im Speicher befindlichen Instanzen zuzugreifen, und die Instanzen bleiben ewig im Speicher.

Zur Verdeutlichung soll der folgende Quellcodeauszug dienen. Dort werden zunächst die Klassen Driver und Vehicle deklariert. Sie besitzen beide je eine Property des jeweils anderen Typs. So lässt sich einem Fahrer ein passendes Fahrzeug zuweisen und einem Fahrzeug ein Fahrer. Anschließend werden zwei Variablen erzeugt – aDriver und aVehicle – und ihnen je eine neu erstellte Instanz der beiden Klassen zugewiesen. Der Referenzzähler für die beiden neuen Instanzen steht an der Stelle somit auf 1. Im nächsten Schritt werden jene Instanzen den Properties driver und vehicle der erstellten Variablen zugeordnet, wodurch sie auch auf die Instanzen verweisen und somit deren Referenzzähler auf 2 steigt. Abschließend gibt das Programm die Variablen durch Zuweisung von nil frei. Sie verweisen also nicht länger auf die Driver- und Vehicle-Instanzen, womit deren Referenzzähler um 1 verringert wird.

class Driver {
var vehicle: Vehicle?
}

class Vehicle {
var driver: Driver?
}

var aDriver: Driver? = Driver()
var aVehicle: Vehicle? = Vehicle()

// Referenzzähler der erzeugten Driver-Instanz: 1

// Referenzzähler der erzeugten Vehicle-Instanz: 1

aVehicle?.driver = aDriver

aDriver?.vehicle = aVehicle

// Referenzzähler der Driver-Instanz: 2
// Referenzzähler der Vehicle-Instanz: 2

aDriver = nil
aVehicle = nil

// Referenzzähler der Driver-Instanz: 1
// Referenzzähler der Vehicle-Instanz: 1

Das Problem besteht nun darin, dass aus dem Code heraus kein Verweis mehr auf die Driver- und Vehicle-Instanz existiert, sie aber noch immer – aufgrund ihrer gegenseitigen Referenzierung – einen Referenzzähler größer 0 besitzen. Folglich bleiben sie im Speicher und lassen sich nicht mehr daraus entfernen; ein Strong Reference Cycle ist entstanden.

Entwickler sind dafür verantwortlich, das Potenzial derartiger Konstrukte zu erkennen und entsprechend darauf zu reagieren. Abhilfe schafft das Schlüsselwort weak, mit dem sich in Swift Properties deklarieren lassen. Wird einer solchen Weak Property eine Instanz zugewiesen, erhöht sich deren Referenzzähler nicht. Sie verweisen lediglich auf eine Instanz im Speicher, während die Strong Properties (die den Standard in Swift darstellen) eine zusätzliche Instanz im Speicher halten und entsprechend deren Referenzzähler erhöhen.

Um nun das beschriebene Problem eines Strong Reference Cycle zu lösen, ist bei sich gegenseitig referenzierenden Properties (wie im Falle der Properties der Klassen Driver und Vehicle) eine der Properties als Weak Property zu deklarieren. Das sorgt dafür, dass die starke Referenzierung zwischen Instanzen der jeweiligen Klassen aufgehoben wird. Der folgende Code zeigt beispielhaft die Deklaration der Property driver der Klasse Vehicle als Weak-Property.

class Vehicle {          
weak var driver: Driver?
}

Wie sich diese kleine Änderung auf das Beispiel von eben auswirkt, ist im folgenden Quellcodeauszug zu sehen. Dort werden erneut Instanzen der Klassen Driver und Vehicle erstellt und deren jeweiligen Properties jene Instanzen zugewiesen. An der Stelle tritt bereits der erste Unterschied auf. Während der Referenzzähler der Vehicle-Instanz um 1 erhöht wird, sobald sie der vehicle-Property der Driver-Instanz zugewiesen wird, ist das umgekehrt bei der Driver-Instanz nicht der Fall. Da sie mit einer Weak Property versehen ist, bleibt der Referenzzähler unverändert auf 1. Sobald das Programm der Variablen aDriver dann nil zuweist, verringert sich der Stand des Referenzzählers der Instanz auf 0 und sie verschwindet aus dem Speicher. Der Zähler der Vehicle-Instanz sinkt im gleichen Zuge um 1, schließlich verweist die vehicle-Property der eben aufgelösten Driver-Instanz nicht mehr auf das Objekt. Sobald aVehicle ebenfalls nil zugewiesen wird, entfernt das Programm die verbliebene Instanz aus dem Speicher und hebt so den Strong Reference Cycle auf.

var aDriver: Driver? = Driver()
var aVehicle: Vehicle? = Vehicle()

// Referenzzähler der erzeugten Driver-Instanz: 1
// Referenzzähler der erzeugten Vehicle-Instanz: 1

aVehicle?.driver = aDriver
aDriver?.vehicle = aVehicle

// Referenzzähler der Driver-Instanz: 1
// Referenzzähler der Vehicle-Instanz: 2

aDriver = nil

// Referenzzähler der Driver-Instanz: 0 (Instanz wird aus Speicher entfernt)
// Referenzzähler der Vehicle-Instanz: 1

aVehicle = nil

// Referenzzähler der Vehicle-Instanz: 0 (Instanz wird aus Speicher
// entfernt)

Übrigens steht außer dem Schlüsselwort weak zum Vermeiden von Strong Reference Cycles noch unowned zur Verfügung. Es hat im Grunde die gleiche Aufgabe und sorgt dafür, den Referenzzähler beim Zuweisen einer Instanz zu einer solchen Unowned Property nicht zu erhöhen und somit lediglich auf die Referenz im Speicher zu verweisen. Der Unterschied besteht darin, dass Unowned Properties nicht auf ein Optional verweisen und somit immer über einen gültigen Wert verfügen. Bei Weak Properties trifft genau der umgekehrte Fall zu: Sie müssen immer und ausschließlich auf ein Optional verweisen.

Unowned Properties – auch als Unowned References bezeichnet – kommen für Eigenschaften zum Einsatz, die immer auf einen validen Wert verweisen, ohne ihn explizit im Speicher zu halten. Ein Beispiel für den Einsatzzweck einer solchen Unownend Reference zeigt das folgende Listing. Darin werden die Klassen Person und CreditCard definiert. Eine Person kann optional eine Kreditkarte besitzen, und jede Kreditkarte ist immer genau einer Person zugeordnet. Hierbei handelt es sich erneut um eine Konstellation, die zu einem Strong Reference Cycle führen kann und entsprechend aufzulösen ist. Da eine Kreditkarte immer einer Person zugeordnet sein muss und die Eigenschaft nicht optional ist, bietet es sich an, den entsprechenden Verweis als Unowned Reference zu implementieren.

class Person {

var name: String

var creditCard: CreditCard?

init(name: String) {

self.name = name

}
}

class CreditCard {

unowned let cardOwner: Person
let cardNumber: UInt
init(cardOwner: Person, cardNumber: UInt) {
self.cardOwner = cardOwner
self.cardNumber = cardNumber
}
}

Abschließend sei noch gesagt, dass jene Regeln nur für Reference Types (sprich Klassen) von Bedeutung sind, da nur sie via Referenz auf ihren zugrunde liegenden Wert im Speicher verweisen. Value Types hingegen (sprich Structures und Enumerations) sind von den genannten Problemen nicht betroffen, da sie das System bei Zuweisung zu einer Variablen oder Konstanten kopiert.

Option Sets in Swift sind mit Bitmasken aus anderen Programmiersprachen vergleichbar: Sie dienen dazu, mehrere zusammenhängende Eigenschaften und Optionen zu definieren, die jeweils einem bestimmten Bit zugeordnet sind. Aus der Kombination jener Eigenschaften und Optionen entsteht immer ein eindeutiger Wert, mit dem sich ermitteln lässt, welche Elemente ausgewählt sind.

Als Grundlage von Option Sets dient das Protokoll OptionSet. Typen, die konform zu jenem Protokoll sind, müssen die Eigenschaft rawValue implementieren, die dazu dient, die ausgewählten Optionen zu identifizieren. Jede einzelne wird dann als Type-Property deklariert und einer Instanz jenes Option Sets mit dem passenden rawValue zugewiesen:

struct Filter: OptionSet {

var rawValue: Int
static let price = Filter(rawValue: 1 << 0)
static let manufacturer = Filter(rawValue: 1 << 1)
static let date = Filter(rawValue: 1 << 2)
static let rating = Filter(rawValue: 1 << 3)
}

Die Structure Filter verfügt über vier Optionen zum Filtern. Da mehrere Filter parallel aktiv sein können, erstellt das Programm die Raw Values der einzelnen Optionen mit dem Bitwise Left Shift Operator, wodurch jede eindeutig einem Bit zugeordnet ist und sich darüber auslesen lässt, welche Optionen gesetzt sind und welche nicht.

Um eine Instanz eines Option Sets in Swift zu erstellen, verwendet man ein Array. Es enthält die zu verwendenden Optionen, wobei Entwickler direkt via vorangestellten Punkt auf letztere zugreifen können, ohne explizit den Typ (im gezeigten Beispiel Filter) angeben zu müssen.

Wie der Einsatz eines solchen Option Set aussehen kann, zeigt der folgende Quellcodeauszug. Darin wird eine Funktion activateFilter(_:) implementiert, die einen Parameter vom Typ Filter erwartet und ihn mit print() ausgibt. Anschließend folgen Aufrufe der Funktion, in denen jeweils unterschiedliche Filter übergeben werden. Den daraus generierten Raw Value gibt das Programm schließlich auf der Konsole aus.

func activateFilter(_ filter: Filter) {
print("Filter: \(filter)")
}

activateFilter([])

// Filter(rawValue: 0)

activateFilter([.price])

// Filter(rawValue: 1)

activateFilter([.date])

// Filter(rawValue: 4)

activateFilter([.manufacturer, .date])

// Filter(rawValue: 6)

activateFilter([.price, .date, .rating])

// Filter(rawValue: 13)

activateFilter([.price, .manufacturer, .date, .rating])

// Filter(rawValue: 15)

Swift hat in den letzten Versionen einige interessante Neuerungen erfahren, die den Umgang mit der Sprache vereinfachen und Entwicklern vielfältigere Möglichkeiten beim Schreiben von Code an die Hand geben. Das Error Handling gehört sicherlich mit zu den spannendsten Ergänzungen, mit denen sich Methoden zum Abfangen von Fehlfunktionen umsetzen lassen. Dank der do-catch-Blöcke lässt sich unkompliziert auf potenzielle Fehler reagieren.

Die Speicherverwaltung war immer ein wichtiges Thema, auch wenn Swift Entwicklern an der Stelle einen Großteil der Arbeit abnimmt. Das Verhindern von Strong Reference Cycles ist dennoch bei der Programmierung zu berücksichtigen. Mithilfe von Weak und Unowned References lässt sich das Problem in Swift aber programmatisch unkompliziert lösen.

Option Sets schlussendlich dienen in Swift dazu, Bitmasken abzubilden und in eigene Typen zu verpacken. Das führt zu gut lesbarem Code, nicht zuletzt dank der verkürzten Schreibweise beim Zugriff auf die einzelnen Optionen eines Option Set mittels vorangestelltem Punkt.

Teil 2 dieser Reihe zur modernen Programmierung mit Swift setzt sich im Detail mit den Besonderheiten der Initialisierung und Vererbung auseinander und stellt das Type Checking sowie das Type Casting der Sprache vor.

Thomas Sillmann
ist iOS-App-Entwickler und Autor. Freiberuflich entwickelt er eigene Apps für den App Store sowie Apps in Form von Kundenaufträgen. Er hat bereits mehrere Fachbücher und Kurzgeschichten veröffentlicht. Sillmann lebt und arbeitet in Aschaffenburg.
(jul [1])


URL dieses Artikels:
https://www.heise.de/-3753777

Links in diesem Artikel:
[1] mailto:jul@heise.de