Swift 4

Die Entwicklung von Swift 4 ist abgeschlossen. Für registrierte Apple-Developer ist Xcode 9 final bereits verfügbar, im App Store wird die Entwicklungsumgebung in den nächsten Tagen auftauchen. Dieser Beitrag fasst die wichtigsten Neuerungen in Swift zusammen. (Erste Version 5.7.2017, finales Update 18.9.2017. Der Artikel berücksichtigt auch die in Swift 3.1 hinzugefügte Neuerungen und geht kurz auf die Pläne für Swift 5 ein.)

Offene Bereiche (Ranges)

Bereiche (Ranges) können jetzt ohne explizite Ober- bzw. Untergrenze formuliert werden (SE 0172). Dabei gibt es drei Syntaxformen:

let ar = Array(1...10)
ar[3...]   // [4, 5, ..., 9, 10]
ar[...3]   // [1, 2, 3, 4]
ar[..<3]   // [1, 2, 3]

Beachten Sie, dass 0 wie in den meisten anderen Programmiersprachen das erste Element bezeichnet, 1 das zweite usw.

Die neue Range-Syntax ist auch in switch-Konstruktionen erlaubt:

let n = 5
switch n {
case ...5:
  print("Bis 5 (inklusive)")
case 6...:
  print("Ab 6 (inklusive)")
default:
  print("Sonst etwas (nicht möglich, aber syntaktisch erforderlich")
}

Zeichenketten (Strings)

Zeichenketten sind wieder Collections. Genau genommen implementiert String das Protokoll StringType, und dieses wiederum BidirectionalCollection und RangeReplaceableCollection.

Deswegen kann eine Schleife über alle Zeichen einer Zeichenkette jetzt ohne die bisher erforderliche characters-Eigenschaft gebildet werden:

let s = "Hello World!"
for c in s {  // Schleife über alle Zeichen
  print(c)
}

Auch andere Operationen lassen sich mit deutlich kompakteren Code durchführen:

print(s.count)  // Ausgabe 12
let r = String(s.reversed())
print(r)       // Ausgabe "!dlroW olleH"

Leider hat man sich nicht dazu durchringen können, den Zugriff auf Teilzeichenketten wie bei Arrays durch Integer-Subscripts zu erlauben. Die Schreibweisen s[3] (viertes Zeichen) oder s[0..<3] bzw. s[..<3] (jeweils die ersten drei Zeichen) sind also NICHT erlaubt.

Mehrzeilige Zeichenketten

Endlich kommt Swift mit mehrzeiligen Zeichenketten zurecht (SE 0168). Diese beginnen mit """ und enden mit """. Der Whitespace vor der zweiten Dreierkombination muss exakt so in allen vorangegangenen Zeilen angegeben werden und wird aus der endgültigen Zeichenkette entfernt.

Innerhalb des Texts muss als einziges Zeichen der Backslash verdoppelt werden, um ihn in die Zeichenkette einzufügen. Die Swift-typische Syntax \(ausdruck) ist auch in mehrzeiligen Zeichenketten erlaubt.

let n = 3
let s = """
  abc\\
  n = \(n)
  def "xy"
  """
print(s)
// Ausgabe: abc\
//          n = 3
//          efg "xy"
Substring-Klasse

Für Teilzeichenketten gibt es die neue Substring-Klasse (SE 0163). Einen Substring erhalten Sie beispielsweise, wenn Sie eine Teilzeichenkette in der Form s[startindex...endindex] ermitteln.

let s = "Lorem ipsum dolor. Sit amet."
let start = s.startIndex
if let end = s.index(of: ".") {
  let sentence1 = s[start...end]  // Datentyp Substring
  print(sentence1)                // Ausgabe "Lorem ipsum dolor."

  // alternativ ohne 'start'-Variable
  let sentence1 = s[...end]       // Datentyp Substring
}

Ein Substring teilt viele gemeinsame Eigenschaften mit der String-Klasse. Der entscheidende Unterschied besteht darin, dass ein Substring die Zeichenkette nicht selbst speichert, sondern auf einen Ausschnitt der zugrundeliegenden String-Instanz verweist. Das ist effizient und spart Speicherplatz. Allerdings kann ein winziger Substring dazu führen, dass ein riesiges String-Objekt nicht aus dem Speicher entfernt werden kann. Wenn ein Substring dauerhaft benötigt wird, sollte er vorher in ein neues String-Objekt umgewandelt werden. In manchen Fällen erkennt Xcode das Problem und zeigt eine entsprechende Warnung an.

Collections, Arrays, Dictionaries und Tupel

swapAt

Zwei Elemente einer MutableCollection können nun mit swapAt vertauscht werden (SE 0173). Das trifft z.B. auf Arrays zu:

var ar = [7, 12, 25, 49]
ar.swapAt(1, 3)
ar  // [7, 49, 25, 12]
Sets und Dictionaries

Eine Menge sinnvoller Erweiterungen hat es bei Sets und Dictionaries gegeben (SE 0165). Beispielsweise lassen sich Dictionaries nun einfacher aus zwei Arrays mit Keys und Values initialisieren:

let deutsch = ["eins", "zwei", "drei"]
let english = ["one", "two", "three"]
let mydict = Dictionary(uniqueKeysWithValues: zip(deutsch, english))
mydict["zwei"]   // two

Auf Dictionaries kann nun die filter-Methode angewendet werden:

let otherDict = [1: "eins", 2: "zwei", 3: "drei", 4: "vier"]
let filteredDict = otherDict.filter {$0.key % 2 == 0}
filteredDict   // [2: "zwei", 4: "vier"]

Beim Zugriff auf Dictionary-Elemente kann nun ein Default-Value angegeben werden. Dieser Wert wird verwendet, wenn es den Key (noch) nicht gibt:

otherDict[1, default: "unbekannt"]  // "eins"
otherDict[5, default: "unbekannt"]  // "unbekannt"

Das vereinfacht viele Algorithmen, z.B. den folgenden, der die Anzahl gleicher Buchstaben zählt (Idee aus SE 0165):

let s = "Lorem ipsum"
var charcount: [Character: Int] = [:]
for c in s {
  charcount[c, default: 0] += 1
}
print(charcount)
// Ausgabe  ["i": 1, "r": 1, "m": 2, "p": 1, "L": 1, 
//           "o": 1, "e": 1, " ": 1, "s": 1, "u": 1]
prefix und drop für Sequences

Sequences können nun mit prefix(while:) bzw. drop(while:) verarbeitet werden. prefix testet die ersten Elemente der Sequenz und liefert alle zurück, bis die while-Bedingung zum ersten Mal nicht zutrifft. Der Rest der Sequenz wird nicht mehr bearbeitet.

drop agiert umgekehrt und verwirft solange Elemente vom Beginn der Sequenz, bis die while-Bedingung zum ersten Mal nicht mehr erfüllt ist. Der Rest der Sequenz wird zurückgegeben.

let lst = [1, 2, 3, 4, 1, 2, 3, 4]
let result1 = lst.prefix(while: {$0 < 3})
result1  // [1, 2]

let result2 = lst.drop(while: {$0 < 3})
result2  // [3, 4, 1, 2, 3, 4]

prefix und drop stehen bereits seit Swift 3.1 zur Verfügung (SE 0045).

Element im Sequences-Protokoll

Das Protokoll Sequence kennt den neuen generischen Typ Element, das wie folgt definiert ist:

associatedtype Element where Self.Element == Self.Iterator.Element

Es kann also als Kurzschreibweise anstelle von Iterator.Element verwendet werden. Das macht den Code übersichtlicher, führt aber zu Inkompatibilitäten, wenn Sie in Swift-3-Code in einem von Sequence abgeleiteten Typ eine eigene Element-Eigenschaft definiert haben.

reduce mit inout-Parameter

reduce(into:combine:) ist eine neue Variante der bekannten reduce-Methode (SE 0171): Sie unterscheidet sich darin, dass der erste Parameter der Closure mit inout deklariert ist. Das ermöglicht die Veränderung dieses Parameters. In manchen Anwendungsfällen, z.B. beim Zusammensetzen eines Arrays, ist diese Variante wesentlich schneller.

Im folgenden Code-Schnipsel wird einmal die herkömmliche reduce-Methode und einmal die neue reduce-Variante verwendet, Schritt für Schritt ein Array um je ein Element zu vergrößern. Bei großen Bereichen ist die zweite Variante um ein Vielfaches schneller.

// traditionelle reduce-Methode, bei großem Range sehr langsam
let range = 0..<10  
let ar1 = range.reduce([]) { $0 + [$1] }

// neu in Swift 4: reduce mit inout-Parameter, bei großem
// Range viel schneller
let ar2 = range.reduce(into: []) {   // Closure für reduce
  (ar: inout [Int], elem) in
  ar.append(elem)
}
print(ar1)  // [0, 1, 2, ..., 9]
print(ar2)  // [0, 1, 2, ..., 9]
Tupel-Doppeldeutigkeit

In Swift 3 war es erlaubt, dass eine Funktion oder Closure mit nur einem Parameter einer Deklaration mit mehreren Parametern entspricht. Der eine Parameter wurde dann als Tupel-Parameter betrachtet.

let fn1 : (Int, Int) -> Void = { // Closure, korrekt nur in Swift 3
   x in
   // x hat den Datentyp  (Int, Int), ist also ein Tupel
}

let fn2 : (Int, Int) -> Void = { // Closure, korrekt in Swift 3 und Swift 4
  x, y in
  // x hat den Datentyp Int
  // y hat auch den Datentyp Int
}

Beginnend mit Swift 4 erfüllt eine Funktion die Deklaration nur, wenn die Anzahl der Parameter übereinstimmt. Wollen Sie wirklich mit Tupeln arbeiten, müssen Sie die Parameterliste in doppelte Klammern stellen, also let fn1 : ((Int, Int)) -> Void ....

In der Praxis wird die striktere Syntax von Swift 4 wohl selten zu Komplikationen führen — und wenn doch, lassen sich diese durch das Einfügen eines Klammernpaars schnell beheben. Details: (SE 0110)[https://github.com/apple/swift-evolution/blob/master/proposals/0110-distingish-single-tuple-arg.md]

Equatable und Hashable

In Swift 4 erfüllen alle elementaren Datentypen die Protokolle Equatable und Hashable. Das gilt nicht nur für Int, String etc., sondern auch für aus der Foundation für Swift adaptierte Strukturen wie Date oder URL (SR 2388).

In Zukunft wird es für die Protokolle Equatable und Hashable sogar eine Default-Implementierungen geben (SE 0185). Es reicht also aus, in der Deklaration einer eigenen Klasse/Struktur/Enumeration einfach die beiden Protokolle zu nennen. Darüber hinaus ist kein eigener Code erforderlich — es sei denn, Sie wollen die Implementierung selbst durchführen. Leider wurde dieses Feature erst Mitte August 2017 beschlossen — zu spät für Swift 4.0. Diese Neuerung wird voraussichtlich ab Swift 4.1 zur Verfügung stehen.

private versus fileprivate

Das Hin und Her bei der Kennzeichnung von Zugriffsebenen hat in Swift 3 für viel Konfusion gesorgt und wurde in Hunderten, wenn nicht in Tausenden Beiträgen in der Swift-Evolution-Mailing-List diskutiert. Viele Entwickler wollten zurück zum Swift-2-Modell, der entsprechende Vorschlag SE 0159 wurde aber abgelehnt.

Für Swift 4 hat man sich letztlich zu einer winzigen Verbesserung durchringen können (SE 0169): Kurz zusammengefasst ist private nun weniger restriktiv und erlaubt den Zugriff auf Elemente eines Typs auch in Erweiterungen, sofern diese in der gleichen Datei formuliert sind. Damit ist das ungeliebte Schlüsselwort fileprivate viel seltener als bisher erforderlich.

// Code-Beispiel aus SE 0169; der gesamte Code muss sich
// in einer Datei befinden
struct S {
    private var p: Int

    func f() { 
        use(g())    // ok, g() is accessible within S
    }
}

extension S {
    private func g() {
        use(p)      // ok, g() has access to p, since it is in an extension on S.
    }
}

extension S {
    func h() {
        use(g())    // ok, h() has access to g() since it defined in the access control scope for S.
    }
}

Eine Kurzzusammenfassung der fünf Schlüsselwörter für Zugriffsebenen sieht damit in Swift 4 so aus:

open         // Zugriff und Vererbung in anderen Modulen
public       // Zugriff auch in anderen Modulen, 
             // aber Vererbung nur im eigenen Modul
internal     // Zugriff und Vererbung nur im eigenen Modul
fileprivate  // Zugriff und Vererbung nur in der aktuellen Datei
private      // Zugriff nur in der aktuellen Klasse sowie in
             // Erweiterungen in der gleichen Datei

KeyPath-Ausdrücke

In aktuellen Swift-Versionen können Sie unkompliziert Referenzen auf Funktionen übergeben:

// schon bisher: Referenzen auf Funktionen
func f(n: Int) -> Int {
  return 2*n
}

let funcref = f
print(funcref(3))  // Ausgabe 6

Neu in Swift 4 ist die Möglichkeit, auch Referenzen auf Eigenschaften zu formulieren (SE 0161). Sie können also gewissermaßen den Pfad zu einer Eigenschaft ausdrücken, die Auswertung aber erst später durchführen. Wie das folgende Beispiel zeigt, müssen Keypath-Ausdrücke mit dem Zeichen \ beginnen. Um den Key Path auf eine konkrete Instanz anzuwenden und eine Eigenschaft auszulesen oder auch zu verändern verwenden Sie die Schreibweise obj[keyPath: kp].

class Rect {
  var x: Int
  var y: Int
  init(x: Int, y: Int) {
    self.x = x; self.y = y
  }
}

let r1 = Rect(x:2, y:3)
let r2 = Rect(x:7, y:2)
let pathToX = \Rect.x        // Datentyp ReferenceWritableKeyPath<Rect, Int>
print(r1[keyPath: pathToX])  // Ausgabe 2
print(r2[keyPath: pathToX])  // Ausgabe 7

(SE 0161) gibt einige weitere Beispiele, die etwas komplexer sind, gegenwärtig aber nicht funktionieren, weil Subscripts in KeyPath-Ausdrücken noch nicht implementiert sind (zuletzt getestet in Xcode 9, Beta 5).

Übrigens gab es schon in Swift 3 mit #keyValue einen Weg, durch Referenzen auf Eigenschaften zuzugreifen. Diese Möglichkeit war aber mit vielen Einschränkungen verbunden und eine Notlösung.

Protokolle / Generics / Extensions

  • In generischen Protokollen können Sie nun für den generischen Datentyp Bedingungen formulieren (Associated Type Constraints, SE 0142), z.B. so: associatedtype SubSequence : Sequence where SubSequence.Iterator.Element == Iterator.Element

  • Swift 3.1 erlaubt verschachtelte Typen mit generischen Parameter innerhalb von generischen Typen (Nested Generics, schon verfügbar seit Swift 3.1, SR 1446).

    struct OuterNonGeneric {
           struct InnerGeneric<T> {}
    }
    struct OuterGeneric<T> {
           struct InnerNonGeneric {}
           struct InnerGeneric<T> {}
    }
    extension OuterNonGeneric.InnerGeneric {}
    extension OuterGeneric.InnerNonGeneric {}
    extension OuterGeneric.InnerGeneric {}
    
  • Es ist nun möglich, einen Datentyp zu formulieren, der sowohl einer Klasse als auch diversen Protokollen entsprechen muss (SE 0156). Die offizielle Bezeichnung lautet Class and Subtype existentials. Die Syntax ist wie bei der Kombination mehrerer Protokolle, wobei am Anfang aber eine Klasse steht: Class & Protocol1 & Protocol2 ....
    protocol P {}       // Code-Beispiel aus SE 0156
    struct S {}
    class C {}
    class D : P {}
    class E : C, P {}
    let u: S & P        // Compiler error: S is not of class type
    let v: C & P = D()  // Compiler error: D is not a subtype of C
    let w: C & P = E()  // Compiles successfully
    
  • Der Datentyp von Subscripts (also obj[whatever]) kann jetzt generisch definiert werden (SE 0148).

  • Extensions können nun so formuliert werden, dass sie nur für bestimmte Datentypen gelten (schon verfügbar seit Swift 3.1, SR 1009).

    // Array erweitern, aber nur für Int-Arrays
    extension Array where Element == Int {
      func sum() -> Int {
        return self.reduce(0) { $0 + $1 }
      }
    }
    let data = [1, 2, 3, 4]
    data.sum()  // Ergebnis 10
    

Codable- und Decodable-Protokolle inkl. JSON-Unterstützung

Das (De-)Serialisieren eigener Daten war bisher recht umständlich. In Swift 4 stehen nun die neuen Protokolle Decodable und Encodable zur Verfügung (oder beide gemeinsam als typealias Codable). Für grundlegende Datentypen gibt es sogar eine Default-Implementierung, d.h., Sie müssen zur Implementierung keinen eigenen Code hinzufügen. Weitere Details: SE 0166 und SE 0167

Die folgenden Zeilen zeigen ein Anwendungsbeispiel für den JSON-Encoder und -Decoder:

struct Author : Codable {
  let name: String
}
struct Book : Codable {
  let title: String
  let isbn: String
  let authors: [Author]
}
let swiftbook = Book(title: "Raspberry Pi",
                     isbn: "978-3-8362-5859-3",
                     authors: [Author(name: "Kofler"),
                               Author(name: "Kühnast"),
                               Author(name: "Scherbeck")])
let encoder = JSONEncoder()
let jsondata = try encoder.encode(swiftbook)
if let jsonstr = String(data: jsondata, encoding: .utf8) {
  print(jsonstr)
  // Ausgabe:
  // {"title":"Raspberry Pi",
  //  "authors":[{"name":"Kofler"}, {"name":"Kühnast"},
  //             {"name":"Scherbeck"}],
  //  "isbn":"978-3-8362-5859-3"}
}
let decoder = JSONDecoder()
let bookcopy = try decoder.decode(Book.self, from: jsondata)
print(bookcopy.title)
print(bookcopy.isbn)

Anstelle von JSONEncoder/JSONDecoder können Sie auch auf die Klassen PropertyListEncoder bzw. PropertyListDecoder zurückgreifen, um eigene Daten in einer Property-List zu speichern bzw. von dort wieder einzulesen.

@objc

In früheren Versionen hat Swift häufig Klassen implizit mit @objc gekennzeichnet, um sie mit Objective-C-Code kompatibel zu machen. In neuen Swift-4-Projekten gibt es derartige Automatismen zwar noch immer (z.B. bei @IBOutlets und @IBActionss), aber die implizite @objc-Kennzeichnung ist viel seltener und muss nun bei Bedarf explizit durch das Voranstellen von @objc erfolgen (SE 0160). Das Ziel dieser Maßnahme ist es, unnötige @objc-Auszeichnungen zu verhindern und damit die Größe des Kompilats zu reduzieren. SE 0160 spricht je nach App von einem Einsparungspotential zwischen 6 und 8 Prozent.

Relativ kompliziert ist allerdings der Umgang mit Swift-3-Projekten: Wenn Sie ein Swift-3-Projekt auf Swift 4 portieren, empfiehlt der Migrator die Option Minimize @objc inference. Das gibt Ihnen die Möglichkeit, Ihren Code entsprechend der Debugging-Ausgaben anzupassen und Ihrem Code gegebenenfalls fehlende @objc-Attribute explizit hinzuzufügen. Dabei helfen Ihnen Warnmeldungen im Debugging-Fenster. Gleichzeitig bleibt aber der Modus Swift 3 @objc inference aktiv, und deswegen zeigt Xcode die folgende Warnung an:

The use of Swift 3 @objc inference in Swift 4 mode is deprecated. Please address deprecated @objc inference warnings, test your code with “Use of deprecated Swift 3 @objc inference” logging enabled, and disable Swift 3 @objc inference.

Diese Warnung werden Sie erst los, wenn Sie den Swift-3-Kompatibilitätsmodus in Xcode in den Build Settings abstellen (siehe auch stackoverflow). Sollten Sie irgendwo ein erforderliches @objc-Attribut vergessen haben, wird Ihr Programm jetzt allerdings einen Fehler auslösen.

In Projekten, die von Swift 3 auf Swift 4 portiert wurden, gilt angeblich ein @objc-Kompatibilitätsmodus. Es kann in den Build Settings des Targets deaktiviert werden.

Der Swift-4-Migrator fügt in manchen Fällen automatisch das @objc-Attribut hinzu, wenn er dies für notwendig erachtet. Das ist z.B. der Fall, wenn Sie #selector verwenden, um eine als action aufzurufende Methode anzugeben (GestureRecognizer etc.).

Keine parallelen Schreibzugriffe auf Variablen

In Swift 3 war es in seltenen Fällen möglich, dass eine Variable bzw. Eigenschaft (genau genommen deren Speicher) gleichzeitig von mehreren Code-Teilen verändert wurde. In Multi-Threading-Anwendungen kann das zu Konflikten führen. Die Fehlersuche ist dann extrem schwierig, weil sich das Problem schwer reproduzieren lässt. Swift 4 macht es besser und verbietet den parallelen Schreibzugriff auf Variablen (siehe SE 0176 sowie das Ownership Manifesto).

Wenn der Swift-4-Compiler Code entdeckt, durch den es zu einem parallelen Schreibzugriff kommen kann, löst er nun einen Fehler aus und Sie sind gezwungen, den Code zu ändern. Der einfachste Fall, bei dem das vorkommen kann, ist eine Swap-Operation. Aus diesem Grund gibt es für Collections die neue swapAt-Methode.

Ich kann zu diesem Kontrollmechanismus leider kein Real World-Beispiel anbieten. Die Beispiele aus SE 0176 wirken eher konstruiert, und in meiner gesamten Code-Basis hat der Compiler keine einzige Stelle entdeckt, die problematisch wäre.

NSNumber-Bridging

Die Umwandlung von NSNumber-Zahlen in native Swift-Datentypen hat in der Vergangenheit viele Probleme verursacht. In Zukunft sollen es etwas weniger werden (SE 0170). Insbesondere wird ein Casting mit as? nur noch dann durchgeführt, wenn es dabei zu keinem Überlauf kommt:

let n = NSNumber(value: 1234)
let i = n as? Int8   // nil (lieferte in Swift 3 das Ergebnis -46)

Failable numeric initializers

Die Datentypen Int, Int8, etc., Float, Float80 und Double haben neue Init-Funktionen in der Form init?(exactly:) erhalten. Wenn eine Konversion möglich ist, liefern sie die gewünschte Zahl, sonst nil. Diese init-Funktion ist schon seit Swift 3.1 verfügbar (SE 0080).

let i = 1234
if let ui = UInt8(exactly: i) {
  print(i)
} else {
  print("UInt8 hat nil zurückgegeben.")
  // das ist bei diesem Beispiel der Fall
}

Swift Package Manager

Große Änderungen hat es im Swift Package Manager gegeben. Die wichtigsten Neuerungen sind in diesem Posting zusammengefasst: Swift 4 Package Manager Update

Swift 3.2

Als Hilfestellung bei der Portierung von Code gibt es Swift 3.2. In Swift 3.2 sind bereits ein Großteil der Neuerungen von Swift 4 verfügbar, lediglich einige Features, die zu Inkompatibilitäten im Vergleich zu Swift 3.1 führen, wurden ausgespart.

Vorhandener Swift-3-Code sollte also vollständig kompatibel zu Swift 3.2 sein. Dennoch können Sie bereits viele Neuerungen von Swift 4 ausprobieren. Swift 3.2 kann insofern ein Zwischenschritt bei der Portierung sein.

Die Portierung kann per Target erfolgen. Bei Projekten, die aus mehreren Targets bestehen, können Sie also 3.2- mit 4.0-Targets kombinieren.

Xcode 9

Xcode sieht grundsätzlich weitgehend unverändert aus. Hinter den Kulissen gibt es aber durchaus Änderungen:

  • Die größte ist der Code-Editor, der intern komplett neu implementiert wurde (laut WWDC-Vorträgen übrigens in Swift). Der Editor soll dadurch viel schneller geworden sein, was mir aber nicht aufgefallen ist. Außerdem erhält er nun endlich Refactoring-Funktionen auch für Swift (aber nicht im Playground, nur in »richtigen« Projekten).

  • Beim Erstellen eines Playgrounds haben Sie nun die Wahl zwischen verschiedenen Typen (Templates): Blank, Game, Map und SingleView.

  • Der Simulator für iOS, tvOS etc. sieht hübscher aus als bisher. Es dürfen nun mehrere Instanzen gleichzeitig laufen, also z.B. ein Simulator für ein iPhone und ein zweiter für ein iPad. Leider laufen SpriteKit-Apps im neuen Simulator deutlich langsamer als unter Xcode 8.

  • Folder im Projekt-Navigator entsprechen nun standardmäßig echten Verzeichnissen innerhalb des Projekts.

  • In Asset-Dateien können nun auch Farben gespeichert werden. Diese kann dann im Code als UIColor(named: "farbname") verwendet werden.

Xcode bietet nun endlich Refactoring-Funktionen an (hier ein Rename).
Es können nun mehrere iOS-Simulatoren gleichzeitig ausgeführt werden.

Migrationserfahrungen

Ich habe mittlerweile alle Beispielprogramme aus meinem Buch auf Swift 4 umgestellt:

  • Etliche Programme laufen ohne jede Änderung bzw. nur mit den wenigen, vom Migrator durchgeführten Änderungen.

  • Fallweise ist Handarbeit erforderlich, vor allem bei Änderungen in den APIs, die der Migrator nicht oder falsch berücksichtigt (geänderte Konstanten/Enumerationen etc.).

  • Code, der Strings verarbeitet, wird vom Migrator in der Regel korrekt angepasst. Die Änderungen fallen geringer aus als erwartet. Nach der Migration bleibt oft die nun überflüssige, aber syntaktisch weiterhin korrekte Eigenschaft characters übrig. Sie kann manuell entfernt werden.

  • Xcode ist beim Auto-Layout noch pingeliger als bisher. Beispielsweise verursachen Textfelder oder Buttons mit einer fix eingestellten Breite jetzt eine Warnung. Es ist oft nicht ganz einfach, Ersatz-Layout-Regeln zu finden, mit denen Xcode einverstanden ist.

  • Die Top und Bottom Layout Guides in iOS-Storyboards gelten als veraltet. In neuen Apps wird empfohlen, die Steuerelemente an einer sogenannten Safe Area auszurichten (wohl wegen des iPhones 8 mit angeblich nicht mehr rechteckigem Display). In neuen Xcode-9-Projekte ist die Safe Area standardmäßig aktiv. Bei alten Projekten kann sie im File Inspector der Storyboard-Datei aktiviert werden.

  • Bei Apps, die auf Ortsdaten zugreifen, sind teilweise neue Einträge in Info.plist notwendig.

  • Bei der Drag&Drop-Programmierung unter macOS gibt es diverse Änderungen, die z.T. größere Umbauten im Code erfordern. Ganz neu: Drag&Drop gibt es nun auch unter iOS 11. Die dafür notwendigen APIs haben aber leider wenig Ähnlichkeiten zu denen von macOS.

  • Damit die SWXMLHash-Bibliothek funktioniert, müssen nun zwei Dateien in das Projekt importiert werden, nämlich SWXMLHash.swift und SWXMLHash+TypeConversion.swift. In der Vergangenheit reichte die erste Datei. Schleifen über Aufzählungen erfordern nun die zusätzliche Eigenschaft all, also z.B. for elem in xml["root"]["catalog"]["book"].all { print(elem) }.

Swift 5

Swift 4 ist noch gar nicht fertig, da starten schon die Arbeiten für Swift 5. Die wichtigsten Neuerungen sollen sein:

  • Generics-Verbesserungen, vor allem zu Optimierung der Standardbibliothek
  • API-Resilience: externe Bibliotheken sollen sich auf API-Konventionen verlassen können
  • Memory Ownership Model: für systemnahe Funktionen soll es ein einfacheres/schnelleres Modell zur Speicherverwaltung geben (wie in Cyclone/Rust)
  • weitere String-Optimierungen
  • neues Fundament zur asynchronen Programmierung (hier sollen nur die Vorarbeiten geleistet werden, die konkrete bzw. vollständige Implementierung ist aber erst für Swift 6 geplant)

Generell sollen Änderungen an Swift, die zu Swift 4 inkompatibel sind, nur in absoluten Ausnahmefällen erlaubt sein. Jeder Vorschlag für eine Änderung kommt nur zur Abstimmung, wenn es bereits konkreten Code zur Implementierung des Features gibt.

Chris Lattner, der eigentlich gar nicht mehr für Apple arbeitet (er ist nach einigen Monaten bei Tesla jetzt Mitglied eines AI-Teams bei Google), hat bereits ein umfangreiches Dokument vorgestellt, das als Ausgangspunkt für die Diskussion zu den asynchronen Erweiterungen von Swift dienen soll. Es verdeutlicht die Komplexität des Themas und die vielen möglichen Spielarten. Für einen Aspekt dieser Ideen, nämlich für die Schlüsselwörter async und await, gibt es sogar schon einen konkreten Implementierungsvorschlag.

Quellen

Apple

Apple / Swift 5

Sonstige