Moderne Programmierung mit Swift, Teil 1
Seite 4: Unowned Properties & Option sets
Ü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
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)