Wichtige Elemente Properties sind ein essenzielles Sprachmerkmal von Swift.

Properties sind ein grundlegender Bestandteil von Apples Programmiersprache Swift (Bild 1). Sie beschreiben Variablen und Konstanten, die innerhalb eines Typs (also beispielsweise innerhalb einer Structure oder Klasse) deklariert sind, und dienen dazu, verschiedene Werte für einen solchen bestimmten Typ zu speichern.

Im Gegensatz zu Variablen und Konstanten sind Properties aber deutlich mächtiger und vielfältiger und bringen einige besondere Funktionen mit. Auch wird zwischen verschiedenen Arten von Properties in

Swift unterschieden, um unterschiedliche Anforderungen und Aufgaben zu erfüllen.

Dieser Artikel stellt diese verschiedenen Arten von Properties und ihre jeweilige Funktionsweise im Detail vor. Im Anschluss kennen Sie alle Facetten von Properties und können Properties entsprechend bestmöglich in Ihren eigenen Projekten verwenden und einsetzen.

Stored Properties

Die Standardform von Properties stellen die sogenannten Stored Properties dar. Diese sind am ehesten mit den herkömmlichen Variablen und Konstanten in Swift vergleichbar. Stored Properties halten Werte im Speicher und geben diese beim Zugriff darauf zurück.

Eine einfache Deklaration zweier Properties vom Typ Int innerhalb einer Structure zeigt das folgende Listing:

struct AStructure {

var variableProperty: Int var constantProperty: Int

}

Die Property variableProperty ist als Variable deklariert, wodurch es sich bei dieser Property um eine sogenannte Read-Write Property handelt; man kann sie also sowohl auslesen als auch ihren Wert ändern. Die Property constantProperty hingegen ist als Konstante deklariert, weshalb es sich dabei um eine sogenannte Read-Only Property handelt. Deren Wert kann also nach einer erstmaligen Zuweisung nicht geändert, sondern nur noch ausgelesen werden.

Über Instanzen der so erzeugten Structure AStructure ist es nun möglich, auf die beiden deklarierten Properties zuzugrei-
fen und diese auszulesen. Im Fall der als Variablen deklarierten Property variableProperty ist es darüber hinaus möglich, den Wert dieser Property zu verändern. Das folgende Listing zeigt die beispielhafte Verwendung und den Zugriff auf die so erstellten Properties mittels der sogenannten Punktnotation:

var myStructure = AStructure(variableProperty: 19, constantProperty: 99) myStructure.variableProperty = 28 print("variableProperty = \(myStructure.variableProperty)") print("constantProperty = \(myStructure.constantProperty)")

// variableProperty = 28 // constantProperty = 99

Auf die in den folgenden Abschnitten vorgestellten weiteren Arten von Properties kann ebenfalls auf die gleiche Art und Weise zugegriffen werden.

Dabei gibt es bei Stored Properties ein Detail zu beachten, wenn diese als Teil einer Structure (sprich eines Value Types in Swift) deklariert sind. Dann nämlich können die Werte von Read-Write Properties wie variableProperty nur geändert werden, wenn die Instanz der Structure ebenfalls als Variable und nicht als Konstante deklariert ist. Den Properties einer Structure, deren Instanz als Konstante deklariert ist, können nämlich nach der erstmaligen Wertzuweisung genau wie bei Read-Only Properties keine neuen Werte mehr zugewiesen werden. Da es sich bei der AStructure-Instanz myStructure um eine Variable handelt, ist eine Änderung der Property variableProperty möglich. Die im folgenden Listing deklarierte Konstante anotherStructure vom Typ AStructure hingegen kann nachträglich keine Änderung an der Property variableProperty vornehmen:

let anotherStructure = AStructure(variableProperty: 19, constantProperty: 99)

anotherStructure.variableProperty = 99 // Fehler: variableProperty der Konstanten anotherStructure kann nicht geändert werden.

Bei Klassen (sprich Reference Types) spielt das beschriebene Problem keine Rolle. Auch wenn eine Instanz eines Reference
SWIFT: PROPERTIES IM DETAIL

Wichtige Elemente

Properties sind ein essenzielles Sprachmerkmal von Swift.

Types selbst als Konstante deklariert ist, können deren Read-Write Properties trotzdem verändert werden.

Lazy Stored Properties

Bei den sogenannten Lazy Stored Properties handelt es sich um eine Sonderform der Stored Properties. Ganz grundlegend entsprechen Lazy Stored Properties in ihrer Funktionsweise den Stored Properties und dienen dazu, bestimmte Werte zu halten, damit auf diese zugegriffen werden kann.

Dabei werden Lazy Stored Properties immer als Variablen mit dem Schlüsselwort var deklariert, was in ihrer eigentlichen Besonderheit begründet ist: Lazy Stored Properties werden erst in dem Moment initialisiert, in dem erstmals auf sie zugegriffen wird. Bis dahin besitzen diese Properties keinen Wert, und da Read-Only Properties aber nach der Initialisierung der zugrunde liegenden Instanz zwingend einen Wert besitzen müssen, können Lazy Stored Properties daher immer nur als Variablen umgesetzt werden.

Bei der Deklaration einer Lazy Stored Property kommt das Schlüsselwort lazy zum Einsatz, das der eigentlichen Property-Deklaration vorangestellt wird. Davon abgesehen ist die Deklaration und Verwendung von Lazy Stored Properties identisch mit der von Stored Properties. Lediglich ein Standardwert beziehungsweise ein Initializer muss zwingend in Verbindung mit einer Lazy Stored Property verwendet werden. Denn Lazy Stored Properties müssen nicht über den zugrunde liegenden Typ initialisiert werden; ihr Vorteil besteht ja gerade darin, dass ihr Wert erst zu einem späteren Zeitpunkt ermittelt wird. Doch für diese Aufgabe muss einer Lazy Stored Property die entsprechende Logik zugewiesen sein, damit sie einen solchen Wert selbsttätig ermitteln kann; ent-
• Developer Discover Design Develop Distribute Support Account Q.

Swift Swft Playgrounds Resources Blog
Swift 3

The powerful programming language that is also easy to learn.

Swift is a powerful and intuitive programming language for macOS, iOS, watchOS and tvOS. Writing Swift code is interactive and fun, the syntax is concise yet expressive, and Swift includes modern features developers love. Swift code is safe by design, yet also produces software that runs lightning-fast.
weder in Form eines Initializers oder durch die simple Zuweisung eines Standardwerts.

Im folgenden Listing ist ein einfaches Beispiel einer Structure namens AnotherStructure zu sehen, die eine einzige Lazy Stored Property deklariert:

struct AnotherStructure {

lazy var lazyStoredProperty = 19

}

In diesem Beispiel wird der Lazy Stored Property ein Standardwert zugewiesen. Alternativ könnte ihr - wie Stored Properties auch - ein Closure zugewiesen werden, das den Standard- beziehungsweise Initialwert der Property bei erstmaliger Verwendung automatisch berechnet, ermittelt und zuweist. Ein solcher Initializer für eine Property wird aber nur maximal einmal aufgerufen, und zwar dann, wenn das erste Mal auf besagte Property zugegriffen wird und ihr zuvor nicht explizit bereits ein anderer Wert zugewiesen wurde.

Lazy Stored Properties kommen im Vergleich zu Stored Properties in der Regel immer dann zum Einsatz, wenn der ihnen zugewiesene Wert recht umfangreich oder komplex ist und gleichzeitig noch nicht zwingend bei Erstellung der zugrunde liegenden Instanz benötigt wird (die Property möglicherweise für die Verwendung der Instanz also gar nicht benötigt wird). In diesen Szenarien sorgen Lazy Stored Properties für geringere Last, da sie erst dann initialisiert werden, wenn sie auch tatsächlich verwendet und genutzt werden.

Computed Properties

Computed Properties unterscheiden sich in ihrer Funktionsweise stark von den zuvor vorgestellten Stored und Lazy Stored Properties. Wie ihr Name bereits andeutet, halten sie keinen spezifischen Wert im Speicher, sondern ermitteln diesen dynamisch. Sie sind also eher mit Methoden vergleichbar, die jeweils eine bestimmte Funktion ausführen, abhängig davon, ob eine Computed Property ausgelesen oder ihr ein neuer Wert zugewiesen wird.

Basis von Computed Properties sind die sogenannten Getter und Setter. Einfach ausgedrückt handelt es sich dabei um jene beschriebenen Methoden, die bei Verwendung einer entsprechenden Computed Property aufgerufen werden. Der Getter kommt dabei zum Einsatz, wenn lesend auf eine Property zugegriffen wird, während der Setter im Fall einer Wertzuweisung zu einer Computed Property ausgelöst wird.

Bei der Deklaration einer Computed Property spielen diese Getter und Setter eine essenzielle Rolle. Eine Computed Property wird dabei zunächst immer mit dem Schlüsselwort var deklariert, niemals mit let. Das liegt darin begründet, dass eine Computed Property immer dynamisch die hinterlegten Befehle ausführt und es so bei jeder Verwendung zu einem anderen Ergebnis kommen kann. Entsprechend ist der Wert einer Computed Property aus Prinzip immer variabel, niemals konstant. Ebenso wird einer Computed Property immer der gewünschte Typ fest mittels Type Annotation zugewiesen.

Nach dieser grundlegenden Deklaration geht es dann aber direkt los mit den Unterschieden zu Stored Properties: ►
"= ■» - q ° pi
Apples Programmiersprache wartet mit interessanten Sprach-merkmalen auf (Bild 1)
Nach der Angabe des Property-Typs folgen geschweifte Klammern, innerhalb derer die Implementierung von Getter und Setter folgt. Der Getter wird dabei in Form des Schlüsselworts get deklariert, wieder gefolgt von einem geschweiften Klammernpaar, innerhalb dessen die Befehle eingetragen werden, die ausgeführt werden sollen, wenn lesend auf die entsprechende Property zugegriffen wird. Als Nächstes folgt der Setter, der mit Hilfe des Schlüsselworts set deklariert wird. Auch im Anschluss an set folgen geschweifte Klammern, zwischen denen die Implementierung des Setters erfolgt. Listing 1 zeigt diesen grundlegenden Aufbau einer typischen Computed Property mit Getter und Setter im Detail.

Wichtig beim Getter: Dieser muss in jedem Fall immer einen passenden Wert des Property-Typs mittels return zurückgeben. Die sonstige Implementierung von Getter und Setter ist vollkommen dem Entwickler selbst überlassen und entspricht der Implementierung von Methoden in Swift.

Um innerhalb des Setters auf den Wert zuzugreifen, welcher der entsprechenden Computed Property zugewiesen wurde, kann innerhalb des set-Blocks auf die temporäre Konstante newValue zugegriffen werden. Diese enthält jenen Wert, den der Setter dann auf die gewünschte Art und Weise weiterverarbeiten kann.

Listing 1: Aufbau von Computed Properties

struct ComputedPropertyStructure { var aComputedProperty: Int {

get {

// Implementierung des Getters

}

set {

// Implementierung des Setters

}

}

}
Listing 2: Abbildung einer Fläche

import Foundation struct Cube {

var width: Double var height: Double var area: Double { get {

return width * height

}

set {

width = sqrt(newValue) height = sqrt(newValue)

}

}

}
Listing 3: Zugriff auf eine Computed Property

var myCube = Cube(width: 5, height: 5) print("Fläche von myCube: \(myCube.area)")

// Fläche von myCube: 25.0

myCube.area = 100

print("Breite von myCube: \(myCube.width)") print("Höhe von myCube: \(myCube.height)")

// Breite von myCube: 10.0 // Höhe von myCube: 10.0
Listing 2 zeigt ein konkretes Beispiel für die Verwendung von Computed Properties. Darin wird eine Structure namens Cube zur Abbildung eines Würfels erstellt, die zunächst über die zwei Stored Properties width und height (zur Abbildung von Breite und Höhe) verfügt. Daneben besitzt die Structure eine Computed Property namens area, die die Fläche eines solchen Würfels zurückgeben soll.

Da sich dieser Wert aus der Breite und Höhe ergibt und somit dynamisch berechnet werden kann, ist die Property als Computed Property umgesetzt. Der Getter gibt dabei schlicht das Ergebnis der Multiplikation der Stored Properties width und height zurück, während der Setter die Wurzel aus einer neu zugewiesenen Fläche zieht und anschließend jeweils width und height als neue Breite und Höhe zuweist. Der neu zugewiesene Wert kann dabei, wie zuvor beschrieben, über die temporäre Konstante newValue innerhalb des Setters ausgelesen werden.

Eine Computed Property selbst kann ansonsten auf dieselbe Art und Weise verwendet werden wie eine Stored Property auch, wie Listing 3 zeigt.

Shorthand Setter

Der Setter einer Computed Property verfügt zusätzlich noch über eine Besonderheit bezogen auf den Wert, der diesem bei einer Zuweisung zu der entsprechenden Computed Property übergeben wird. Innerhalb des Setters lässt sich standardmäßig über die temporäre Konstante newValue auf den übergebenen Wert beim Setzen einer Computed Property zugreifen.

Dieses Verfahren wird auch als Shorthand Setter Declaration bezeichnet, da die eigentliche Deklaration des übergebenen Wertes automatisch durch Swift erfolgt. Es lässt sich aber auch problemlos ein anderer Bezeichner für diese temporäre Konstante definieren. Dazu setzt man innerhalb runder Klammern direkt nach Deklaration des Setters über das Schlüsselwort set den gewünschten Bezeichner, der dann statt newValue innerhalb des Setters zur Verfügung steht. Die Betonung liegt dabei explizit auf statt, denn wenn ein eigener Bezeichner definiert ist, steht newValue nicht länger automatisch im Setter zur Verfügung.

In Listing 4 ist nochmals die Structure Cube aufgeführt, dieses Mal allerdings mit einer eigenen Deklaration für den Bezeichner des Setters. Dieser wird dort auf den Namen newArea festgesetzt, womit beim Ändern der Computed Property area
der neu zugewiesene Wert über diesen neuen Bezeichner abgerufen werden kann. Für die Zuweisung eines Wertes zu dieser Property hat diese Änderung keinerlei Einfluss.

Read-Only Computed Properties

Computed Properties müssen überdies nicht zwingend über einen Setter verfügen. Soll beispielsweise eine Computed Property lediglich dynamisch einen Wert ermitteln und zurückgeben (zum Beispiel die Fläche eines Würfels), wobei ihr aber kein neuer Wert zugewiesen werden soll, dann kann sie als sogenannte Read-Only Computed Property deklariert werden. In diesem Fall verfügt die Computed Property über keinen Setter, und die Zuweisung eines Wertes zu dieser Property würde umgehend zu einem Compiler-Fehler führen (ähnlich dem Versuch, einer bestehenden Konstante in Swift einen neuen Wert zuzuweisen).

Um eine Read-Only Computed Property zu erstellen, muss lediglich der set-Block bei der Deklaration der Property weggelassen werden. In Listing 5 ist ein Beispiel dazu zu sehen. Dort wird eine neue Structure namens Person deklariert, die über die beiden Stored Properties firstName und lastName (zur Abbildung von Vor- und Nachnamen einer Person) verfügt. Zusätzlich bestitzt die Structure noch eine Computed Property fullName, deren Getter den vollen Namen durch die Zusammensetzung von firstName und lastName zurückgibt. Da über diese Property kein neuer Name gesetzt werden soll, fehlt der entsprechende set-Block, wodurch die Property zugleich zu einer Read-Only Computed Property wird.

Swift bietet für die Deklaration derartiger Read-Only Computed Properties auch eine Kurzschreibweise an. Da nämlich eine Read-Only Computed Property ausschließlich über den Getter-Block verfügt, kann in diesem Fall auch vollständig auf die Erstellung des get-Blocks verzichtet werden und statt-dessen die Implementierung des Getters direkt innerhalb der geschweiften Klammern nach der Deklaration der Computed Property stattfinden. Wie das aussieht, zeigt Listing 6.

Die letzte Form von Properties in Swift sind die sogenannten Type Properties. Type Properties unterscheiden sich von

Listing 4: Deklaration eines eigenen Bezeichners

import Foundation

struct Cube {

var width: Double var height: Double var area: Double {

get {

return width * height

}

set(newArea) { width = sqrt(newArea) height = sqrt(newArea)

}

}

}
Listing 5: Read-Only Computed Property
struct Person {
var firstName : String
var lastName: String
var fullName: String {
get {
return "\ (firstName) \(lastName)"
}
}
}

Listing 6: Kurzschreibweise
struct Person {
var firstName : String
var lastName: String
var fullName: String {
re turn "(fi rstName) \(lastName)"
}
}

den bisher vorgestellten Properties dahingehend, dass sie nicht an eine spezifische Instanz eines Typs gebunden sind, sondern einmalig übergreifend für den gesamten Typ gelten. Der Wert einer Type Property ist also an jeder Stelle innerhalb eines Projekts derselbe, und wird er an einer Stelle geändert, ist die Änderung auch für alle sichtbar.

Type Properties können sowohl als Stored Properties (nicht als Lazy Stored Properties!) wie auch als (Read-Only) Computed Properties umgesetzt werden. Statt über eine Instanz des zugrunde liegenden Typs werden sie direkt über den zugehörigen Typ aufgerufen, wie wir gleich sehen werden.

Deklariert werden Type Properties mit Hilfe des Schlüsselworts static. In Klassen für Type Properties, die als Computed Properties umgesetzt sind, kann alternativ das Schlüsselwort class verwendet werden. Das ist immer dann notwendig, wenn die entsprechende Property in Subklassen überschreibbar sein soll.

In Listing 7 ist ein Beispiel zur Deklaration und Verwendung von Type Properties aufgeführt. aStoredTypeProperty ist dabei eine Stored Property, während aComputedTypeProperty eine Read-Only Computed Property ist. Beide Properties können nur direkt über den Typ TypePropertyStructure, innerhalb dessen sie deklariert sind, verwendet und aufgerufen werden. Über Instanzen dieser Structure stehen die Properties nicht zur Verfügung.

Eingesetzt werden Type Properties typischerweise immer für Informationen eines Typs, die für jede Instanz dieses Typs gleichermaßen gelten. Bildet man beispielsweise eine Kfz-Werkstatt ab, könnte diese als Type Property die aktuelle Anzahl an zu reparierenden Wagen speichern. Ganz allgemein gilt aber schlicht die Faustregel: Bezieht sich eine Proper- 
ty explizit auf eine bestimmte Instanz eines Typs, dann wird sie als Instance Property (dem Gegenteil einer Type Property) umgesetzt; bezieht sie sich dagegen auf eine allgemeine Information für den zugrunde liegenden Type, ist der Einsatz einer Type Property sinnvoller.

Property Observer

Zum Abschluss dieses Artikels möchte ich noch auf eine Technik eingehen, die für Stored Properties (nicht Lazy Stored Properties) in Swift zur Verfügung steht: die sogenannten Property Observer. Diese können jeder Stored Property hinzugefügt werden und dienen dazu, über Änderungen einer Stored Property zu informieren. Wann immer sich dann eine entsprechende Property ändert, können eigens definierte Befehle ausgeführt werden. Das kann man beispielsweise dazu nutzen, um bei Änderung einer bestimmten Property eine zugehörige View zu aktualisieren. Dabei gibt es generell zwei Ausführungen von Property Observer:

willSet: Der willSet-Block wird aufgerufen, bevor sich der Wert der zugehörigen Property ändert. Innerhalb dieses Blocks lässt sich über die Konstante newValue auf den Wert zugreifen, der der Property gleich zugewiesen wird.

Listing 7: Deklaration von Type Properties

struct TypePropertyStructure {

static var aStoredTypeProperty = "Type property" static var aComputedTypeProperty: String { return "Computed type property"

}

}

TypePropertyStructure.aStoredTypeProperty =

"Stored type property" print

("\(TypePropertyStructure.aStoredTypeProperty)")

print

("\(TypePropertyStructure.aComputedTypeProperty)")
Listing 8: Deklaration von Property Observer

struct Person { var name: String { willSet {

print("Der neue Name lautet: \(newValue)")

}

didSet {

print("Der alte Name lautete: \(oldValue)")

}

}

}

var me = Person(name: "Thomas") me.name = "Thomas Sillmann"
didSet: Der didSet-Block wird aufgerufen, nachdem sich der Wert der zugehörigen Property geändert hat. Innerhalb dieses Blocks lässt sich über die temporäre Konstante old-Value auf den Wert zugreifen, den die Property vor der Änderung besaß.

Eine Stored Property kann beide oder auch nur einen der genannten Property Observer implementieren, je nachdem, welche Information benötigt wird (vor Durchführung einer Änderung oder nach Durchführung einer Änderung). Property Observer werden dabei auf dieselbe Art und Weise implementiert wie der Getter- und Setter-Block von Computed Properties, nur dass anstelle des jeweiligen Blocks das Schlüsselwort willSet beziehungsweise didSet für die Deklaration verwendet wird.

In Listing 8 sehen Sie ein Beispiel zur Verwendung und Implementierung von Property Observer. Dort wird eine Structure Person mit einer einzigen Stored Property name deklariert, wobei diese Property beide der genannten Property Observer implementiert. In beiden Fällen wird ein einfacher print()-Befehl ausgeführt, um so innerhalb von willSet den neuen Wert und innerhalb von didSet den alten Wert der Property auszugeben.

Ähnlich wie beim Setter in Computed Properties kann der Bezeichner der temporären Konstante, die jeweils innerhalb eines Property Observers zur Verfügung steht, geändert werden. Dazu wird der gewünschte Bezeichner innerhalb von runden Klammern nach dem Schlüsselwort willSet beziehungsweise didSet aufgeführt. Innerhalb des jeweiligen Blocks kann dann ausschließlich dieser eigens definierte Bezeichner verwendet werden, um auf den neuen Wert (in willSet) beziehungsweise den alten Wert (in didSet) zuzugreifen.

Fazit

Properties gehören bei der Programmierung in Swift zu den wichtigsten Elementen. Sie dienen aber nicht einfach nur der Speicherung von Werten verschiedener Eigenschaften einer Instanz, sondern können noch deutlich mehr. Mittels Computed Properties können sich Properties wie Methoden verhalten und dynamisch Werte ermitteln und zurückgeben. Und Property Observer erlauben das einfache Überwachen von Änderungen an Properties sowie die passende Implementierung von Befehlen, die bei Durchführung einer entsprechenden Änderung ausgeführt werden sollen. Hat man die Funktionen und Möglichkeiten von Properties erst einmal verinnerlicht, erlauben sie es einem, den eigenen Code besser zu strukturieren und zu optimieren.
Thomas Sillmann

ist iOS-App-Entwickler und Autor. Freiberuflich tätig entwickelt er eigene Apps für den App Store sowie Apps in Form von Kundenaufträgen. www.thomassillmann.de





You may also like

Keine Kommentare:

Blog-Archiv

Powered by Blogger.