Moderne Programmierung mit Swift, Teil 3

Seite 2: Weitere Operatoren, Dateien und Interfaces

Inhaltsverzeichnis

Equivalence Operators prüfen zwei Instanzen eines Typs auf Gleichheit. Sie bezieht sich in dem Kontext nicht auf die zugrundeliegende Speicheradressen der Instanzen, sondern deren Werte. Dabei ist das Problem zu lösen, festzustellen, unter welchen Bedingungen zwei Instanzen eines Typs gleich sind. Im Falle der bisher beispielhaft verwendeten Structure Rectangle bedeutet Gleichheit, dass Breite und Höhe zweier Instanzen demselben Wert entsprechen. Swift aber weiß das nicht, und ein Versuch, zwei Instanzen der Structure mit == oder != miteinander zu vergleichen, endet in einem Compiler-Fehler.

Eine derartige Aufgabe lässt sich in Swift lösen, indem man entsprechende Operator Methods für die beiden genannten Vergleichsoperatoren in eigenen Typen implementiert. Dazu erstellt man auf die mehrfach gezeigte Weise eine Methode innerhalb des gewünschten Typs, die == oder != heißt, und deklariert für die Methoden zwei Parameter desselben Typs. Als Rückgabewert dient eine Bool-Instanz. Sie gibt in Bezug auf die Rectangle-Structure im Falle des Operators == immer true zurück, wenn Breite und Höhe der beiden übergebenen Parameter identisch sind – andernfalls false. Im Falle des Operators != negiert das Programm das Ergebnis.

struct Rectangle {

var width: Int
var height: Int

static func == (firstRectangle: Rectangle, secondRectangle: Rectangle) -> Bool {
if firstRectangle.width == secondRectangle.width && firstRectangle.height ==
secondRectangle.height {

return true
}
return false
}

static func != (firstRectangle: Rectangle, secondRectangle: Rectangle) -> Bool {

if firstRectangle == secondRectangle {
return false
}

return true
}
}

Zum Verdeutlichen der beiden Operator Methods auf Basis von Equivalence Operators zeigt der folgende Codeauszug deren beispielhafte Verwendung. Zunächst generiert das Programm dazu zwei unterschiedliche Instanzen vom Typ Rectangle, die jeweils über eine unterschiedliche Breite und Höhe verfügen, sodass sie nicht identisch sind. Anschließend werden sie via == als Teil einer if-Abfrage miteinander verglichen.

let firstRectangle = Rectangle(width: 19, height: 99)
let secondRectangle = Rectangle(width: 50, height: 30)

if firstRectangle == secondRectangle {
print("Die Rechtecke sind identisch.")
} else {
print("Die Rechtecke sind nicht identisch.")
}

// Die Rechtecke sind nicht identisch.

Die bisher gezeigten Operatorbeispiele wurden dazu verwendet, standmäßige verfügbare Swift-Operatoren zu überschreiben und für eigene Zwecke einzusetzen. Es besteht darüber hinaus die Option, gänzlich eigene Operatoren zu erstellen und auf vergleichbare Art wie bisher gezeigt in eigenen Typen zu implementieren. So lassen sich insgesamt drei Arten eigener Operatoren in Swift umsetzen: Infix, Prefix und Postfix. Die letzten beiden sind bereits bekannt, der Infix-Operator braucht im Gegensatz zu ihnen zwei Instanzen ein- und desselben Typs, um über sie eine Operation durchzuführen. Ein Beispiel ist der Additionsoperator +.

Um auf der Grundlage einen eigenen Operator in Swift zu erstellen, bringt jede der eben genannten Arten ein passendes SchlĂĽsselwort mit: infix, prefix und postfix, die zusammen mit dem SchlĂĽsselwort operator zu verwenden sind. Das folgende Beispiel zeigt, wie ein neuer Postfix-Operator +++ erstellt wird:

postfix operator +++ 

Zuerst ist das Schlüsselwort für die gewünschte Operator-Art anzugeben, gefolgt von operator. Anschließend folgt die Bezeichnung des gewünschten Operators, der sich fortan durch Operator Methods so nutzen lässt, wie im Beitrag gezeigt.

Im folgenden Code ist ein passendes Beispiel zu sehen. Der zuvor neu erstellte Operator +++ ist dabei so implementiert, dass er Höhe und Breite des Rechtecks, für das er aufgerufen wird, um den Wert 30 erhöht (↲ im Listing weißt lediglich auf formatbedingte Zeilenumbrüche hin).

postfix operator +++

struct Rectangle {
var width: Int
var height: Int

static postfix func +++ (rectangle: Rectangle) -> Rectangle {

let updatedRectangle = Rectangle(width: rectangle.width + 30, height: ↲
rectangle.height + 30)
return updatedRectangle
}
}

Das folgende abschlieĂźende Beispiel zeigt den praktischen Einsatz des Operators und das durch ihn herbeigefĂĽhrte Ergebnis.

var rectangle = Rectangle(width: 19, height: 99)

rectangle = rectangle+++

print("Breite: \(rectangle.width)")

print("Höhe: \(rectangle.height)")

// Breite: 49
// Höhe: 129

Im Gegensatz zu vielen anderen Programmiersprachen wie Objective-C findet bei Swift keine dateibezogene Unterscheidung zwischen Interface und Implementierung statt. Private und öffentliche APIs stehen bisweilen direkt nebeneinander. Um den Zugriff auf einzelne Typen, Properties und Funktionen zu regeln, gibt es die sogenannte Access Control. Sie bietet durch Access Levels relativ feingranulare Einstellungsmöglichkeiten, um Zugriff auf bestimmte Funktionen zu erlauben oder zu verhindern.

Bevor ein genauerer Blick sinnvoll ist, sind noch zwei Begriffe zu klären, die für den API-Zugriff in Swift eine bedeutende Rolle spielen: Module und Source File. Bei Letzterem handelt es sich schlicht um eine einzelne Swift-Datei mit der Dateiendung .swift. Sie enthält den Quellcode für einen oder mehrere Typen, ganz gleich ob Enumeration, Structure oder Klasse. Ein Module fasst ein oder mehrere Source Files zusammen und bündelt sie so zu einem Produkt. Das Paket einer iOS-App ist ein typisches Beispiel für ein Module, ebenso wie ein Framework wie UIKit oder MapKit. Möchte man die Funktionen eines Modules in Swift nutzen, ist es zunächst via import-Anweisung zu importieren.

In Swift stehen fĂĽnf Access Level zur VerfĂĽgung. Sie lassen sich auf alle API-relevanten Elemente der Sprache anwenden, beispielsweise auf das Deklarieren von Klassen, Properties und Methoden. Sie werden schlicht dem jeweiligen Element vorangestellt, um so das Access Level zu definieren.

Los geht es mit dem restriktivsten aller Access Level: private. So deklarierte Eigenschaften und Funktionen lassen sich nur innerhalb des Typs aufrufen und verwenden, in dem sie definiert wurden. Im folgenden Beispiel ist zur Demonstration eine simple Structure AStruct mit einer Private Property namens privateProperty deklariert. Darüber hinaus überschreibt die Structure den Default Initializer und greift darin auf die Private Property zu, um ihr einen Wert zuzuweisen. Innerhalb der Structure ist der Zugriff auf privateProperty kein Problem, außerhalb davon aber unmöglich. Das zeigt das anschließende Erstellen einer Instanz der Structure AStruct und der Versuch, auf die Property privateProperty zuzugreifen, was umgehend in einem Compiler-Fehler endet.

struct AStruct {
private var privateProperty: String
init() {
privateProperty = "Private property"
}
}

private let myStruct = AStruct()

myStruct.privateProperty // Fehler: privateProperty steht
// auĂźerhalb der Implementierung von AStruct nicht zur VerfĂĽgung.

Übrigens weicht Apple mit der kommenden Version 4 von Swift das Verhalten des Private Access Level ein wenig auf. Erstellt man beispielsweise mit Swift 3 eine Extension eines Typs, ist innerhalb der Extensions kein Zugriff auf Private Properties möglich; sie sind mit einem anderen Access Level zu versehen. Mit dem kommenden Swift 4 können aber auch Extensions eines Typs auf Private Properties zugreifen.