Swift 4.2

Zusammen mit Xcode 10 wird in den nächsten Tagen Swift 4.2 freigegeben. Entwickler mit einem Apple-Developer-Zugang können den Goldmaster von Xcode 10 bereits jetzt herunterladen, alle anderen werden das Update in ein paar Tagen über den App Store automatisch erhalten.

Apple weicht mit dem Release vom bisher jährlichen Major-Release-Zyklus ab. Swift 5 soll es erst im Frühjahr 2019 geben. (Voraussichtlich ein bis zwei Monate später wird es dann vorauss. auch die Neuauflage meines Swift-Buchs geben.)

Swift 4.2 ist ein inkrementelles Update zu Swift 4.1. Es gibt also keine inkompatiblen Änderungen, aber diverse Erweiterung der Sprache oder der Standardbibliothek. Eine Referenz aller Neuerungen finden Sie auf der swift-evolution-Seite. Ich greife hier nur die wichtigsten Punkte heraus.

Zufallszahlen

Die für viele Programmierer(innen) sichtbarste Verbesserung in Swift 4.2 besteht darin, dass es endlich einen einfachen und konsistenten Weg gibt, um Zufallszahlen zu generieren:

let n = Int.random(in: 0..<10)    // zwischen 0 (inkl.) und 9 (inkl.)
let f = Float.random(in: 0..<1)   // zwischen 0.0 (inkl.) und 1.0 (exkl.)
let d = Double.random(in: -1..<1) // zwischen -1.0 (inkl.) und 1.0 (exkl.)
let b = Bool.random()             // true/false

var lst = [17, 33, 51, 67]
lst.shuffle()                     // verändert lst
let lst2 = lst.shuffled()         // erzeugt neue Liste, lst bleibt unverändert
let itm = lst.randomElement()     // wählt ein zufälliges Element aus, Datentyp Int?

let s = "abcde"                   // zufälliges Zeichen
let someChar = s.randomElement()  // Datentyp String.Element?

// zufälliges Passwort aus 8 Kleinbuchstaben
let abc = "abcdefghijklmnopqrstuvwxyz"
let pw = String((1...8).map {_ in abc.randomElement()! })

let s = "Hello, World!"
let ar3 = s.shuffled()            // z.B. ["l", "o", "l", ...]
let c = s.randomElement()         // z.B. "o"
let char = "abcde".randomElement()

Effizienzprobleme: Int.random ist ca. um den Faktor 8 langsamer als die Low-Level-Funktion arc4random_uniform. Wenn Sie wirklich viele Zufallszahlen benötigen, sollten Sie bei dieser Funktion bleiben!

toggle() für Bool

Der Datentyp Bool kennt nun die Methode toggle. Sie macht aus true -> false bzw. als false -> true:

var b = true
b.toggle()
b  // false

Bearbeitung von Collections (allSatisfy und removeAll)

Swift kennt neue Methoden zur Bearbeitung von Collections. Mit allSatisfy können Sie überprüfen, ob alle Elemente der Collection eine Bedingung erfüllen:

let lst = [2, 44, 120]
if(lst.allSatisfy {$0 % 2 == 0}) {
    print("Alle Zahlen sind gerade.")
}

removeAll entfernt alle Elemente, die eine Bedingung erfüllen. Die Methode verändert die Aufzählung in-place und ist besonders effizient implementiert (siehe SE-0197):

var data = [1, 2, 3, 4, 7, 12, 25, 30]
data.removeAll() {$0 % 2 == 0}  // alle geraden Zahlen entfernen
data                            // [1, 3, 7, 25]

Enumerationen mit CaseIterable

Wenn Sie Enumerationen mit dem Protokoll CaseIterable ausstatten, steht die Eigenschaft allCases zur Verfügung. Sie enthält ei

enum Colors: CaseIterable {
  case red, blue, green, black, white
}

for c in Colors.allCases {
  print(c)
}

Dynamische Eigenschaften (@dynamicMemberLookup)

Das neue Attribut @dynamicMemberLookup gibt Ihnen die Möglichkeit, Typen zu definieren, auf deren Daten Sie in der Form obj.name [= ...] zugreifen können, obwohl die Eigenschaft name gar nicht statisch definiert ist. Stattdessen wird bei unbekannten Eigenschaftsnamen eine subscript-Methode aufgerufen. Um deren Implementierung müssen Sie sich selbst kümmern. Die folgenden Zeilen geben ein einfaches Beispiel:

@dynamicMemberLookup
struct Person {

  private var dynamicdata = [String: String]()

  subscript(dynamicMember member: String) -> String {
    get {
      return dynamicdata[member] ?? ""
    }
    set {
      dynamicdata[member] = newValue
    }
  }
}

var p = Person()
p.name = "Michael Kofler"
p.city = "Graz"
p.tel  = "+43 123 4567 8901"
p.city  // "Graz"
p.mail  // ""

Die Struktur Person hat also gar keine Eigenschaften. Sie können aber beliebige Daten in der Form obj.name = "xxx" speichern und später in der Form obj.name wieder auslesen. Wenn name unbekannt ist, liefert die subscript-Methode in der obigen Implementierung einfach eine leere Zeichenkette. (Sie könnten hier auch einen Fehler auslösen oder auf eine andere Art und Weise reagieren.)

Die hier präsentierte Implementierung hat den Nachteil, dass ausschließlich Zeichenketten gespeichert und wieder ausgelesen werden können. Diese Einschränkung lässt sich umgehen, indem Sie subscript-Methoden für mehrere Datentypen implementieren. Das macht allerdings später den Zugriff umständlicher, weil der Datentyp für den Compiler immer eindeutig erkennbar sein muss:

@dynamicMemberLookup
struct Person {
  private var dynamicStringData = [String: String]()
  private var dynamicIntData = [String: Int]()

  // dynamische Zeichenketten-Eigenschaften
  subscript(dynamicMember member: String) -> String {
    get {
      return dynamicStringData[member] ?? ""
    }
    set {
      dynamicStringData[member] = newValue
    }
  }

  // dynamische Int-Eigenschaften
  subscript(dynamicMember member: String) -> Int {
    get {
      return dynamicIntData[member] ?? 0
    }
    set {
      dynamicIntData[member] = newValue
    }
  }
}

// Anwendung
var p = Person()
p.name = "Michael Kofler"
p.id = 123

// OK, weil n explizit als Int deklariert wurde
let n: Int = p.id
print(n)

// Fehler, Ambiguous use of 'subscript(dynamicMember:)',
// weil der Datentyp unklar (String oder Int?)
print(p.id)

Weitere Hintergrundinformationen und Anwendungsbeispiele finden Sie in der Dokumentation zu SE-0195. Weitere Beispiele gibt Hacking with Swift und Ray Wenderlich.

Eigene Hash-Funktion einfacher gemacht

Seit Swift 4.1 reicht es oft aus, eigene Strukturen oder Enumerationen mit dem Protokoll Hashable auszustatten (siehe auch Swift 4.1). Swift kümmert sich dann selbst um die Implementierung der Hash-Funktion, sofern der Typ selbst aus lauter Elementen zusammengesetzt ist, die selbst Hashable sind. Das trifft u.a. für alle Standarddatentypen zu (Int, String etc.). Allerdings ist dieser Automatismus mit einigen Einschränkungen verbunden und funktioniert insbesondere für Klassen nicht.

Für den Fall, dass Sie die Hash-Funktion selbst programmieren müssen (class) oder wollen, unterstützt Sie Swift 4.2 dabei nun besser als zuvor (siehe auch SE-0206): Zur Erfüllung des Protokolls Hashable müssen Sie nunmehr die Methode hash implementieren (nicht mehr var hashvalue). An dieses Methode wird eine Instanz einer Hasher-Struktur übergeben. Eigene Daten, die in die Hash-Funktion einfließen sollen, übergeben Sie einfach mit hasher.combine(daten).

class Person : Hashable, Equatable {
  var name: String
  var tel: String
  var mail: String

  init(name: String, tel: String, mail: String) {
    self.name = name
    self.tel = tel
    self.mail = mail
  }

  // für Equatable
  static func == (lhs: Person, rhs: Person) -> Bool {
    return lhs.name == rhs.name &&
      lhs.mail == rhs.mail &&
      lhs.tel == rhs.tel
  }

  // für Hashable
  func hash(into hasher: inout Hasher) {
    hasher.combine(name)
    hasher.combine(tel)
    hasher.combine(mail)
  }
}

Aus Kompatibilitätsgründen reicht es für das Hashable-Protokoll aktuell aus, entweder wie bisher var hashvalue oder (neu in Swift 4.2) func hash zu implementieren. Allerdings gilt hashvalue ab Swift 4.2 als deprecated und wird längerfristig eliminiert werden (siehe auch den Abschnitt Source compatibility in SE-0206).

Kompatibilität, Xcode

Bei schnellen Tests mit den mehr als 100 Beispielprogramme aus dem Swift-Buch bin ich beim Update von 4.1 auf 4.2 auf keine nennenswerten Probleme gestoßen.

Xcode meckert jetzt auch bei Steuerelementen ohne Layout-Constraint: Auto Layout Localization: Views without any layout constraints may clip their content or overlap other views. Bisher wurden Steuerelemente ganz ohne Constraints still akzeptiert, was gerade in Testprogrammen praktisch war. Dass Xcode hier nun noch pingeliger ist, führt zu einer Menge zusätzlicher Warnungen, die aber natürlich nichts mit Swift zu tun haben.

Playgrounds sind leider instabil und zickig wie eh und je. Bei meinen Tests habe ich deswegen etliche Komplettabstürze von Xcode erlebt. Neuerdings wirft Xcode im Playground ständig mit unknow context-Warnungen um sich. Mitunter behauptet Xcode, dass Code im Playground syntaktisch falsch sei, obwohl derselbe Code in einem ‚richtigen‘ Xcode-Projekt wunderbar funktioniert. Schade, dass Apple die Playgrounds nicht so hinbekommt, dass man gerne damit arbeitet!

Endlich für Ubuntu 18.04, in Zukunft wohl auch »offizielles« Docker-Image

(Update 20.9.2018) Eine nicht unwesentliche Kleinigkeit habe ich übersehen: Swift 4.2 steht nun (endlich) auch für das aktuelle Ubuntu 18.04 zum Download zur Verfügung. Längerfristig soll Swift aber auch »offiziell« für Docker angeboten werden (siehe den entsprechenden Forum-Thread). Das ist schon länger mein bevorzugter Weg, wenn ich Swift außerhalb der macOS-Welt brauche.

Quellen und Links