zurück zum Artikel

Das erwartet Entwickler mit Swift 5.1

Nikolaj Schumacher
Das erwartet Entwickler mit Swift 5.1

Gleichzeitig mit der Präsentation der neuen iPhones hat Apple am vergangenen Dienstag den "Gold Master" von Xcode 11 zum Download bereitgestellt. Im Gepäck ist die neue Swift-Version 5.1. Ein offizielles Release der Programmiersprache steht allerdings, ebenso wie die Verfügbarkeit im App Store, noch aus.

Seit Swift Open Source ist, gibt es eigentlich keine Apple-typischen Keynote-Überraschungen mehr. Alle Sprachfeatures werden im Swift-Evolution-Forum [1] diskutiert und bewertet, ehe sie in der Sprache landen. Nicht so jedoch auf dem WWDC 2019: Apple wollte die versammelte Entwicklergemeinde mit einem neuen UI-Framework beeindrucken [2]. SwiftUI ist tief mit der Sprache verwurzelt und benötigt Features, die der Swift-Community noch nicht vorgelegt worden waren. Nach drei Revisionen sind zumindest die Property Wrapper nun offiziell in der Sprache angekommen. Function Builder, die die DSL-Fähigkeiten (Domain-Specific Languages) der Sprache ausbauen, sind noch nicht so weit. Man kann sie daher – trotz Produktiveinsatz in SwiftUI – noch als experimentell einstufen. Ein Dutzend weiterer Änderungen haben ihren Weg in Swift 5.1 über den üblichen Prozess geschafft.

Die Stabilisierung der binären Schnittstelle (ABI) in Swift 5.0 war ein großer Einschnitt. Seitdem ist es nicht mehr nötig, die Swift-Laufzeitumgebung als Teil jeder App auszuliefern. Apple liefert (und aktualisiert) sie zusammen mit dem Betriebssystem. Im Umkehrschluss bedeutet das allerdings, dass ab Swift 5.1 erstmals nicht mehr jede neue Swift-Funktion auf älteren Betriebssystemen verfügbar ist. Das betrifft vor allem die Standardbibliothek, reine Compiler-Features lassen sich auch auf älteren Versionen einsetzen. Theoretisch könnte Apple zwar iOS 12 noch ein kleines Update mit aktualisiertem Swift spendieren, zu erwarten ist das freilich nicht. Auch Mac-Entwickler müssen sich bis zum Release von macOS Catalina gedulden. Wer die neuen Funktionen nur in einem Swift-Playground ausprobieren möchte, kann dort aber einfach "iOS" als Plattform wählen.

Mehr Infos

Swift auf der heise MacDev

Die Programmiersprache und SwiftUI spielen auch eine große Rolle auf der heise MacDev [3], der neuen Konferenz der Heise Medien für Entwickler für Apples Plattformen. Hierzu kann man sich noch bis 30. September inklusive Frühbucherrabatt registrieren und demnach gegenüber dem späteren Standardpreis sparen.

Dafür bietet Swift 5.1 Mechanismen, um binäre Frameworks vorwärtskompatibel zu gestalten. Das neue Compiler-Argument -enable-library-evolution sorgt unter anderem dafür, dass Structs und Enums binärkompatibel wachsen dürfen. Es ist also nicht nötig, Platz für mögliche zukünftige Felder zu reservieren, wie es etwa in C-Structs üblich ist. Außerhalb der Apple-Welt, zum Beispiel auf Linux-Plattformen, bleibt die ABI auch weiterhin offen für Veränderungen.

Passend dazu erlaubt das neue Schlüsselwort some es jetzt, konkrete Typen in der öffentlichen API zu abstrahieren. Bislang war das für Protokolle mit sogenannten Associated Types nicht möglich. Die zugehörige Fehlermeldung "Protocol 'Equatable' can only be used as a generic constraint because it has Self or associated type requirements" dürfte schon manchen Entwickler frustriert haben. Diese neuen "opaquen" Typen garantieren außerdem, dass immer derselbe konkrete Typ zurückgegeben wird. Nur welcher das genau ist, erfährt der Aufrufer nicht.

struct Person {
var name: String
var age: Int

var id: some Hashable {
return name
}
}

Der konkrete Typ der ID ist nach außen nicht mehr sichtbar.

Da das Mitliefern der Standardbibliothek in Apps wegfällt, darf man damit rechnen, dass sie in Zukunft schneller wächst. In Swift 5.1 sind nun generische SIMD-Datentypen und Operatoren dabei, die auf bis zu 64 Datensätzen gleichzeitig arbeiten, ohne dass CPU-spezifischer Code zu schreiben ist.

let x = SIMD4<Int>(1, 2, 3, 4)
let y = SIMD4<Int>(1, 2, 2, 4)

let isEqual: SIMDMask<SIMD4<Int.SIMDMaskScalar>> = x .== y

Bei dem Ergebnis des komponentenweisen Vergleichs handelt es sich hier um einen Vektor aus boolschen Werten. Bei diesen Typnamen darf man wohl für Swifts Typherleitung besonders dankbar sein.

Die Unterschiede zwischen zwei Collections lassen sich jetzt per Diff-Algorithmus auflisten und abstrakt speichern, und ein neues Protokoll namens Identifiable hilft, Referenzsemantik auf Value Types wie Structs zu übertragen. Dazu muss es nicht viel leisten: Es liefert lediglich einen standardisierten Namen für eine ID.

struct Person : Identifiable {
var id: Int
var name: String
}

let before = [
Person(id: 1, name: "Tim"),
Person(id: 2, name: "Steve")
]
let after = [
Person(id: 1, name: "Timmy"),
Person(id: 3, name: "Steve")
]

let diffs = after.difference(from: before) { $0.id == $1.id }

for diff in diffs {
switch diff {
case let .insert(index, element, _):
print("\(element.name) an Index \(index) eingesetzt")
case let .remove(index, element, _):
print("\(element.name) an Index \(index) entfernt")
}
}

let after2 = before.applying(diffs)
let before2 = after.applying(diffs.inverse())

Die eingangs erwähnten Property Wrapper sind eine der größten Änderungen dieses (zumindest der Versionsnummer nach) kleinen Updates. Mit ihnen erlaubt Swift erstmals benutzerdefinierte Attribute. Sie weisen den Compiler an, Getter- und Setter-Aufrufe einer Property an ein drittes Objekt zu delegieren. Dadurch lassen sich beispielsweise Thread-lokale, Copy-on-Write- oder atomare Properties erzeugen, ohne dass der Compiler eine maßgefertigte Unterstützung dafür bieten muss. Auch "lazy" Properties, die Swift seit Version 1 kennt, ließen sich durch einen Property Wrapper ersetzen. Entwickler können die Wrapper verketten und parametrisieren. In der Praxis können sie daher Boilerplate deutlich reduzieren oder Code instrumentalisieren.

@propertyWrapper
struct UserDefault<T> {
let key: String
let `default`: T

var wrappedValue: T {
get {
return UserDefaults.standard.object(forKey: key) as? T ?? self.default
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
}

struct LoginSettings {
@UserDefault("stay_logged_in", default: false)
var stayLoggedIn: Boolean
}

Dynamisch delegieren lassen sich Properties auch, und zwar schon seit Version 4.2. Ursprünglich eingeführt wurde die @dynamicMemberLookup-Annotation, um die Interoperabilität mit Python und anderen dynamischen Sprachen zu verbessern. Das ist vor allem ein Projekt des Swift-Begründers Chris Lattner, der, inzwischen bei Google angestellt, Swift mit TensorFlow verheiraten möchte. Bislang funktionierte das allein auf String-Basis. Das ist ideal zum Ankoppeln an eine fremde Runtime, bringt aber die üblichen Nachteile durch Tippfehler sowie fehlende Compiler-Analyse und Autovervollständigung. Mit Swift 5.1 sind zudem typsichere keyPath-Parameter zur Delegation erlaubt.

struct Person {
let name: String
}

struct Car {
let licensePlate: String
}

@dynamicMemberLookup
struct TimeTraveler<T> {
let traveler: T

subscript<U>(dynamicMember keyPath: KeyPath<T, U>) -> U {
traveler[keyPath: keyPath]
}
}

let marty = TimeTraveler(traveler: Person(name: "Marty"))
let delorean = TimeTraveler(traveler: Car(licensePlate: "OUTATIME"))

print(marty.name)
print(delorean.licensePlate)

Passend zum Wegfall von Code wird die Swift-Syntax noch einmal bündiger. Besteht eine Funktion oder ein Getter aus einem einzelnen Ausdruck, kann das return zukünftig entfallen. In Closures war das bereits erlaubt.

struct Angle {
var degrees: Double
var radians: Double { degrees / 180.0 * .pi }
}

Self ist nun analog zu self überall als Platzhalter für den aktuellen Typen erlaubt und ersetzt den etwas sperrigen Ausdruck type(of: self). Auch das manuelle Schreiben von Struct-Initializern wird häufiger überflüssig, denn der synthetische Code dafür ist ein Stück flexibler geworden. Bereits an der Deklaration initialisierte Properties lassen sich jetzt wahlweise als Argument überspringen.

struct WallClock {
var hours: Int
var minutes: Int
var seconds: Int = 0
var milliseconds: Int = 0
}

WallClock(hours: 12, minutes: 30, seconds: 30, milliseconds: 500)
WallClock(hours: 12, minutes: 30, seconds: 30)
WallClock(hours: 12, minutes: 30)

Einen Kompatibiltätsmodus für Änderungen gibt es dieses Mal nicht. Die einzig signifikante Inkompatibilität dürfte die Anwendung des try?-Operators auf einen bereits optionalen Wert sein. War das bislang ein doppeltes Optional, bleibt es nun bei einem. Größere Code-Änderung bleiben Swift-Entwicklern also erspart – zumindest in diesem Jahr.

Nikolaj Schumacher
ist Swift-Fan und -Skeptiker der ersten Stunde. Bei JetBrains ist er heute für den Swift-Support in AppCode verantwortlich und freut oder ärgert sich täglich über Swift.
(ane [4])


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

Links in diesem Artikel:
[1] https://forums.swift.org/c/evolution
[2] https://www.heise.de/news/SwiftUI-erlaubt-plattformuebergreifende-Apps-4438340.html
[3] https://heise-macdev.de/programm.php
[4] mailto:ane@heise.de