Updates und Errata zum Buch »Swift 2«

Das Manuskript des Buchs wurde mit Xcode 7 Beta 5 abgeschlossen. Leider hat Apple die Swift-Standardbibliothek in der letzten Beta 6 nochmals umgebaut. Das Konzept eines Code-Freeze ist bei Apple offensichtlich unbekannt.

Im Folgenden sind neben diversen Fehler im Text auch alle Syntax-Änderungen zwischen Xcode 7 Beta 5 und Xcode 7 Final dokumentiert. Ergänzend dazu gibt es eine aktualisierte PDF mit den Abschnitten 6.3 und 6.4.

Updates Standardfunktionen (PDF)

Beispieldateien: Die Beispieldateien zum Buch sind aktuell zu Xcode 7 Final, d.h., dort sind die hier beschriebenen Änderungen bereits nachvollzogen.

eBook: Am 6.11.2015 wurde die eBook-Fassung aktualisiert. Wenn Sie das eBook nach diesem Datum kaufen, erhalten Sie automatisch die korrigierte Fassung. Wenn Sie das eBook schon vorher gekauft haben, loggen Sie sich einfach nochmals auf http://booksonline.rheinwerk-verlag.de/library/ ein und laden die neue Version des eBooks herunter. In der neuen Fassung sind alle bis Anfang Nov. bekannten Fehler korrigiert. Seither neu bekannte Fehler sind in der folgenden Zusammenstellung mit == neu == gekennzeichnet.

Ca. seit Anfang des Jahres wird auch ein aktualisierter Nachdruck ausgeliefert, in dem alle bis Nov. bekannten Fehler behoben sind.

Swift 2.2: Swift 2.2 ist ein Zwischenschritt hin zu dem für Herbst geplanten großen Update auf Swift 3. Für den Blog zu dieser Webseite habe ich einen ausführlichen Überblick über alle bekannten Neuerungen in Swift 2.2 verfasst. Dort finden Sie auch die für Swift 2.2 überarbeiteten Beispieldateien.

Updates und Errata

Seite 45 (stringByAppendingPathComponent)

Swift 2.0 Final akzeptiert die Methode stringByAppendingPathComponent nicht mehr für Strings. Das Beispiel auf S. 45 muss daher so umformuliert werden:

let temp = NSString(string: NSTemporaryDirectory())
  .stringByAppendingPathComponent("mein-temp-verzeichnis")
Seite 52 (Verzeigungen/switch)

Die ersten zwei Sätze zu switch sind durcheinander gekommen. Es sollte nur ein Satz sein, und dieser muss wie folgt lauten:

Bei der switch-Konstruktion ist ein default-Block zwingend erforderlich und muss nach allen anderen case-Blöcken formuliert werden.

Seite 73 (Bitshift-Operatoren)

Ich habe links und rechts verwechselt ;-)

>> schiebt natürlich nach rechts, << nach links.

Seite 98 (join)

Die join-Methode ist nun logischer organisiert:

item.join(data) --> data.joinWithSeparator(item)
Seite 99 (distance)

Aus distance wurde distanceTo:

distance(start, end) --> start.distanceTo(end)
Seite 106 (split und join)

Aus der split-Funktion wurde ein Methode. Der Code für das Beispiel auf S. 106 oben sieht jetzt so aus:

let txt1 = "Swift ist eine Programmiersprache."
let words = txt1.characters.split() {$0 == " "}.map { String($0) }
// words = ["Swift", "ist" ...]
let txt2 = words.joinWithSeparator(" ")
txt1 == txt2                     // true
Seite 109 — 110 (advance)

Aus der advance-Funktion wurde die Methode advancedBy:

advance(x, n) --> x.advancedBy(n)
Seite 115/116, NSDate-Komponenten

Der Beispielcode auf S. 116 oben passt nicht zur Beschreibung auf S. 115 unten. Der Code muss wie folgt aussehen:

let now = NSDate()  // z. B. "Jul 14, 2015, 8:57 AM"
let cal = NSCalendar.currentCalendar()
// Tag, Monat und Jahr extrahieren
// comps hat den Datentyp NSDateComponents
let comps = cal.components(
  [.Day, .Month, .Year], fromDate: now)
// Zugriff auf die Einzelkomponenten
comps.year    // 2015
comps.month   // 7
comps.day     // 14
Seite 120 (toInt / Int)

Das Code-Beispiel in der Seitenmitte verwendet die nicht mehr vorhandene Methode toInt. Richtig sieht der Code so aus:

if let n = Int(inputString)  {  // n hat den Datentyp Int
  for var i=1; i<=n; i++ {
    print(i)
  }
}
Seite 136 (ArraySlice)

ArraySlice-Elemente übernehmen jetzt den Indexbereich des ursprünglichen Arrays.

var nmbs = Array(1...10)   // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
var sub = nmbs[3...7]      // [4, 5, 6, 7, 8]

Bis Xcode 7 Beta 5 konnten die sub-Elemente mit sub[0], sub[1] bis sub[4] gelesen bzw. verändert werden. Ab Beta 6 bleiben die Indizes wie im Original-Array, also sub[] bis sub[3] bis sub[7].

sub[3]                     // 4
sub[5] = -5
sub                        // [4, 5, -5, 7, 8]
Seite 137 (splice)

Die splice-Methode wurde in insertContentOf umgenannt:

x.splice(y, atIndex: n) --> x.insertContentOf(y, at: n)
Seite 141 (swap)

swap löst in aktuellen Swift-Versionen einen Fehler aus, wenn versucht wird, ein Array-Element mit dem gleichen Element zu vertauschen (also swap(&ar[n], &ar[n])). Deswegen ist im Shuffle-Algorithmus ein entsprechender Test erforderlich:

var ar = Array(1...10)
for i in 0..<ar.count {
  let n = Int(arc4random_uniform(UInt32(ar.count-1)))
  if n != i {  swap(&ar[i], &ar[n]) }
}
print(ar)    // Ausgabe z. B. [3, 10, 5, 1, 9, 4, 6, 8, 2, 7]
Seite 142 (print)

Anstelle des print-Parameters appendNewLine gibt es nun den Parameter terminator:

print(x, appendNewLine: false) --> print(x, terminator: "")
Seite 189 — 194 (Standardfunktionen)

Einige weitere Standardfunktionen sind nun zu Methoden geworden:

advance(x, n)        --> x.advancedBy(n)
distance(start, end) --> start.distanceTo(end)
dropFirst(data)      --> data.dropFirst()
dropLast(data)       --> data.dropLast()
join(sep, data)      --> data.joinWithSeparator(sep)
prefix(data, n)      --> data.prefix(n)
split(data)          --> data.split()
suffix(data, n)      --> data.suffix(n)

Die Funktion lazy existiert nicht mehr. In diesem Fall gibt es auch keine Ersatzmethode. Stattdessen realisieren lazy Collections jetzt die Protokolle LazyCollectionType oder
LazySequenceType bzw. sind von den Klassen LazyCollection, LazyFilterCollection, LazyFilterGenerator, LazyFilterIndex, LazyFilterSequence, LazyMapCollection, LazyMapGenerator, LazyMapSequence oder LazySequence abgeleitet.

Seite 195 (print)

print ist zwar noch immer eine Funktion, hier haben sich aber die Parameter geändert. Aus appendNewLine: true/false wurde terminator. Mit diesem Parameter können Sie die Zeichenkette übergeben, mit der die Ausgabe beendet wird (normalerweise ein Zeilenumbruch). Mit dem weiteren optionalen Parameter separator kann angegeben werden, welche Zeichenkette zwischen den einzelnen Ausgaben gestellt werden soll (normalerweise ein Leerzeichen). Beispiele:

let a="a", b=3, c=1.7
print(a, b, c)                                 // Ausgabe "a 3 1.7"
print(a, b, c, separator:"--")                 // Ausgabe "a--3--1.7"
print(a, b, c, separator:"--", terminator:"|") // Ausgabe "a--3--1.7|"

print im Playground In Xcode 7.0 Final bleiben mitunter print-Ausgaben im Playground unsichtbar. Wird derselbe Code in einem kompiliertem Programm ausgeführt, erscheinen die Ausgaben.

Seite 200/201 (forEach)

Die forEach-Implementierung in Swift 2.0 Final macht Probleme. Das Beispiel von S. 201 oben liefert in Swift 2.0 den für mich nicht nachvollziehbaren Fehler (Function signature not compatible):

let data = Array(1...20)
data.filter( {$0 % 3 == 0} )
  .map( {$0*$0} )
  .forEach( { print($0) } )

Wird der Code wie folgt umformuliert, funktioniert er. Allerdings ist im Playground keine Ausgabe sichtbar. Das hat wohl mit print zu tun.

let data = Array(1...20)
let result = data.filter( {$0 % 3 == 0} )
  .map( {$0*$0} )
result.forEach( { print($0) } )

In Swift 2.1 (Xcode 7.1) funktionieren beide Varianten, d.h. das Problem in Swift 2.0 war offensichtlich ein Bug. Aber selbst in Swift 2.1 ist im Playground keine Ausgabe zu sehen. Führen Sie den Code dagegen in einem gewöhnlichen Projekt aus, dann funktioniert alles.

Seite 255 (Subscripts)

Aus der advance-Funktion wurde die Methode advancedBy. Das Listing in der Seitenmitte muss so aussehen:

var ar = [17, 29, 31, 47]
var dict = ["GRZ":"Graz", "VIE":"Wien", "SZG":"Salzburg"]
var s = "Hello World!"
ar[2]                           // 31, Datentyp Int
dict["VIE"]                     // "Wien", Datentyp String?
s[s.startIndex...s.startIndex.advancedBy(3)]     // "Hell"
Seite 257 (Beschreibung von 07/schach2.playground) == neu ==

Die Methode getColRow Code enthält gleich zwei Fehler. Zuerst muss die Bedingung xxx.count != 2 lauten, nicht xxx.count == 2. Auch die zweite if-Bedingung ist verkehrt formuliert. Die korrigierte Code sieht so aus:

// Umrechnung "e3" --> (4, 2)
private static func getColRow(pos:String) -> (Int, Int) {
  if pos.unicodeScalars.count != 2 {  // korrigiert
    print("wrong position")
    return (-1, -1)
  }

  let code1 =
    Int(pos.lowercaseString.unicodeScalars.first!.value)
  let code2 = Int(pos.unicodeScalars.last!.value)
  if code1>=97 && code1<=104 && code2>=49 && code2<=56 {
    return (code1 - 97, code2 - 49)   // A mit B vertauscht
  } else {
    print("wrong position")           // B mit A vertauscht
    return (-1, -1)
  }  
}
Seite 279 (sort) == neu ==

Anstelle von geos.sort( {...}) muss es geos.sortInPlace( {...} ) heißen.

Seite 281 (optionale Methoden eines Protokolls) == neu ==

Bei der Implementierung einer Methode, die im Protokoll als optional gekennzeichnet ist, verlangt der aktuelle Swift-Compiler nun das Schlüsselwort @objc auch vor der Methode. Der Code für die Quadrat-Klasse sieht daher so aus:

// Quadrat-Klasse
class Square : Geometry {
  var side:Double
  init(_ s:Double) { side = s }
  @objc var area:Double  { return side*side }
}
Seite 284 (join)

Aus join wurde joinWithSeparator. Die beiden join-Aufrufe in der Person-Methode sehen nun so aus:

let telnr = tel.joinWithSeparator(" ")
let email = mail.joinWithSeparator(" ")
Seite 315 (Optionals mit »try?«)

Zu dem im Buch beschriebenen Schlüsselwort try! ist die Variante try? hinzugekommen. try? ruft eine Funktion oder Methode auf, die einen Fehler verursachen kann. Tritt tatsächlich ein Fehler auf, liefert try? den Wert nil zurück, andernfalls das Ergebnis. Die folgenden Zeilen machen dies deutlich:

enum MyErrors : ErrorType {
  case TooSmall
  case TooBig(maximum:Int)
  case Missing
  case Other(explanation:String)
}

// f1, f2 und f3 können Fehler auslösen
func f1(n:Int) throws -> Int {
  if n<0   { throw MyErrors.TooSmall }
  if n>100 { throw MyErrors.TooBig(maximum: 100) }
  return n+1
}

var result1 = try? f1(10)  // Datentyp Int?
print(result1)             // Ergebnis Optional(11)
result1 = try? f1(1000)
print(result1)             // Ergebnis nil

Bei Methoden, die an sich schon Optionals zurückgeben, führt try? zu doppelten Optionals, also z.B. zum Datentyp Int?? (entspricht Optional<Optional<Int>>):

func f5(n:Int) throws -> Int? {
  if n<0   { return nil }
  if n>100 { throw MyErrors.TooBig(maximum: 100) }
  return n+1
}

let result5 = try? f5(10)  // Datentyp Int??
if let n = result5 {       // Datentyp Int?
  if let m = n {           // Datentyp Int
    print(m)
  }
}
Seite 323 (split)

Aus split wurde eine Methode. let lines = ... im Listing auf S. 323 sieht nun so aus:

let lines = s.characters.split() {$0 == "\n"}.map { String($0) }
Seite 479 (split)

Aus der split-Funktion wurde ein Methode:

...
let lines = txt.characters.split() {$0 == "\n"}
  .map { String($0) }
for line in lines {
  let columns = line.characters.split() {$0 == "\t"}
    .map { String($0) }
Seite 541 (stringByAppendingPathComponent)

Swift 2.0 Final akzeptiert die Methode stringByAppendingPathComponent nicht mehr für Strings. Die betreffende Variable (hier path) muss explizit als NSString deklariert werden:

if let path:NSString = pfd.first {
  return path.stringByAppendingPathComponent("rates.plist")
}

Diese Änderung ist undokumentiert, über die Auswirkungen wurde im Apple-Entwickler-Forum heftig diskutiert.

Seite 716 (pathExtension)

Gleiches Problem wie auf S. 541, die Eigenschaft pathExtension muss explizit auf NSString angewendet werden und funktioniert nicht für String-Variablen. Deswegen muss fname mit :NSString deklariert werden.

if draginfo.numberOfValidItemsForDrop == 1,
  let data = pboard.propertyListForType(NSFilenamesPboardType) as? [String],
  fname:NSString = data.first
{
  switch fname.pathExtension.lowercaseString {
Seite 727 (stringByAppendingPathComponent)

Gleiches Problem wie auf S. 541, die Zeile let fname = ... muss wie folgt formuliert werden:

let fname = NSString(string: folder)
  .stringByAppendingPathComponent(name)

Letzte Änderung 21.12.2015. Vielen Dank an alle Leser, die mir Feedback geben!