Kapitel 8 Wie Sie das System mit Cocoa-Objekten voll ausreizen

Inhalt

Ich hatte Ihnen vorher versprochen, dass Sie mit Swift-Objekten sehr viel Arbeit sparen. In diesem Kapitel erfahren Sie nun, was die Software-Entwickler von Apple schon so alles geleistet haben, um Ihr Leben noch weiter zu erleichtern  sie haben nämlich ganze Bibliotheken von Objekten geschrieben, die Ihnen häufig wiederkehrende Aufgaben abnehmen.

Zunächst einmal zeige ich Ihnen, wie Sie die Hilfetexte zu den Objekten abrufen. Dann lernen Sie das Wichtigste dieser Objekte kennen: die Klasse NSString. Diese benutzen Sie immer dann, wenn Sie es mit Texten zu tun haben. Sie haben bereits Texte kennengelernt und mit String-Objekten gearbeitet, aber ich kann Ihnen versprechen, dass noch viel mehr Funktionalität darauf wartet, von Ihnen entdeckt zu werden!

Ein Praxisbeispiel  der Passwort-Generator  zeigt dann, wie Sie die verschiedenen Funktionalitäten, die Sie bisher kennengelernt haben, kombinieren, um neue Aufgaben zu lösen.

8.1 So holen Sie Hilfe

Wie Sie in Kapitel 1 gesehen haben, basiert ein wesentlicher Teil des Betriebssystems von iPhone, iPad und MacOS X auf dem »Cocoa«- bzw. dem »Cocoa Touch«-Framework. Cocoa stellt Ihnen eine ganze Reihe von Funktionen, Methoden und Klassen zur Verfügung, um auch anspruchsvolle Aufgaben schnell und einfach zu lösen. Ein Beispiel war die Funktion arc4random_uniform in Abschnitt 6.1.1. In diesem Kapitel werde ich unter anderem die Klasse NSString besprechen.

Was stellen Sie aber eigentlich ganz konkret mit NSString-Objekten an? Diese Frage stellt sich bei Cocoa-Objekten häufiger, denn sie sind oft sehr leistungsfähig und selbst erfahrene Entwickler kennen nicht alle Möglichkeiten auswendig.

IMG

Abbildung 8.1 Übersichtsseite der Hilfe und Dokumentation in Xcode

Aus diesem Grund besitzt Xcode eine sehr umfangreiche Sammlung an Dokumentations- und Hilfeseiten. Einsehen lassen sich diese, indem Sie aus dem Menü HELP den Menüpunkt DOCUMENTATION AND API REFERENCE auswählen. Dann erscheint ein neues Fenster, das einem Webbrowser ähnelt, siehe Abbildung 8.1. Leider ist die gesamte Dokumentation auf Englisch, aber dafür ist sie recht ausführlich und behandelt alle Funktionen vollständig.

Die einfachste Methode, eine Dokumentationsseite zu finden, besteht darin, die Dokumentation nach bestimmten Stichworten gezielt zu durchsuchen  das funktioniert dabei genau so, wie Sie Informationen im Internet durch Google finden. Geben Sie in das Suchfeld neben dem Lupensymbol einen Suchbegriff ein. Versuchen Sie es mal mit dem Stichwort »uiview«. Nach wenigen Augenblicken erscheint nun ein Menü mit wichtigen Themen zum Suchbegriff UIVIEW, ganz ähnlich, wie Sie auch in einem Browser Hilfe bekommen würden, siehe Abbildung 8.2. Wählen Sie den ersten Eintrag mit dem roten C aus (wobei das C hier eine Abkürzung für CLASS, also Klasse bedeutet).

IMG

Abbildung 8.2 Finden der Dokumentation der Klasse »UIView«

Wenn Sie die Seite mit der Dokumentation der Klasse UIView gefunden haben, sehen Sie eine sehr lange und ausführliche Beschreibung der Klasse, der Anfang ist in Abbildung 8.3 gezeigt. Die Seite enthält eine allgemeine Einführung, Hinweise, wie die Klasse zu verwenden ist, was sie alles für erweiterte Funktionalitäten bietet, und auch Hinweise für den Fall, dass Sie beabsichtigen, eine Unterklasse von UIView zu erstellen. Dann folgt eine Auflistung aller Methoden und Eigenschaften sowie eine ausführliche Beschreibung jeder einzelnen.

IMG

Abbildung 8.3 Ausführliche Dokumentationsseite der Klasse »UIView«

Ich gebe zu, dass dies sehr einschüchternd wirken kann. Wirklich für jede einzelne der Hunderten von Klassen von iOS und MacOS X eine solche Dokumentation zu lesen, ist ein Ding der Unmöglichkeit. Glücklicherweise brauchen Sie nicht alles zu lesen und erst recht nicht alles zu verstehen. Die Dokumentation ist deswegen so ausführlich, weil dort alles enthalten ist, was jemals im Zusammenhang mit einer bestimmten Klasse für den Entwickler relevant sein könnte. In diesem Sinne ist die Dokumentation lediglich als Referenz zu verstehen, aber nicht als Lehrbuch für die Verwendung einzelner Klassen.

Aber was ist nun wirklich alles mit Strings in Swift möglich? Die komplette Referenz bekommen Sie (wieder mal) über die Dokumentationsseite, siehe einen Screenshot davon in Abbildung 8.4. Dieses Mal ist alleine die Auflistung der einzelnen Funktionalitäten fünf Bildschirmseiten lang. Diese Klasse hat es wirklich in sich!

IMG

Abbildung 8.4 Ausführliche Dokumentationsseite der Klasse »NSString«

Aber auch hier gilt wieder: Sie brauchen nicht die gesamte Funktionalität auswendig zu kennen, sondern es gibt nur eine kleine Menge an Funktionen, die Sie häufig verwenden werden. Und genau darauf möchte ich mich jetzt konzentrieren.

8.2 NSString: Dieses Wort ist mächtiger als das Schwert

Bisher haben Sie für Texte immer den Datentyp String verwendet. Diese Klasse für Texte ist neu mit Swift entstanden. NSString hingegen ist eine Klasse von Cocoa und schon einige Jahrzehnte älter als Swift. Sie wurde daher schon recht lange für alle Aufgaben verwendet, die mit Texten in Programmen zu tun haben. Ein String in Swift darf auf alle Fähigkeiten, d.h. alle Methoden und Eigenschaften von NSString zugreifen. Xcode sorgt dafür, dass ein String in Swift so richtig funktioniert.

Sie haben Strings bereits kennengelernt und wissen, wie Sie diese erzeugen: indem Sie den Text in doppelte Anführungszeichen schreiben. Dabei gibt es noch die folgenden Dinge zu beachten:

"\"Hallo\""
"\\\""

Beispielhaft sind die folgenden Objekte Strings:

"Hallo, Welt" 
"Hallo, \"Kleiner\"" 
"Grün und schön"

      "IMG"

8.2.1. Zahlen, Formate und Ersetzungen

Im Folgenden stelle ich Ihnen die typischen Aufgaben vor. Ich empfehle Ihnen, zu diesem Zweck einen Playground zu erstellen. Geben Sie ihm den Namen »Stringspielchen«. Sie haben bisher die print-Funktion benutzt, um Texte in Ihrem Programm auszugeben. Dabei haben Sie einen Text in Anführungszeichen geschrieben. Außerdem konnten Sie Variablen und Konstanten ausgeben, indem Sie diese innerhalb von runden Klammern, vor denen ein Backslash steht, geschrieben haben.

In diesen Fällen ist der Parameter der print-Funktion nichts anderes als ein String! Dadurch ist nicht die print-Funktion in der Lage, die ganzen Texte zu handhaben – nein, in Wahrheit war es die ganze Zeit die Klasse String!

Variablen und Konstanten lassen sich also durchaus in Texte schreiben und ausgeben. In begrenztem Umfang dürfen Sie sogar einfache Rechnungen innerhalb der runden Klammern schreiben. Beispielsweise sind die folgenden Konstanten ganzzahlRechnung und fliesskommaRechnung zwei völlig korrekte Stringkonstanten:

// Erzeuge eine Ganzzahl--Konstante. 
let zaehler = 1 
 
// Dieser String lautet "Testausgabe: 4". 
let ganzzahlRechnung = "Testausgabe: \(zaehler+1+2)" 
 
// Dieser String lautet "Testausgabe: 4.0". 
let fliesskommaRechnung = "Testausgabe: \(Double(zaehler)+1.0+2.0)"

In der Konstanten ganzzahlRechnung wurde zu einer Konstanten zwei weitere Zahlen hinzuaddiert. Alle Zahlen waren Ganzzahlen vom Typ Int, deswegen ist das Ergebnis auch eine Ganzzahl und wird in den Text eingefügt. Genauso wird in der Konstanten fliesskommaRechnung verfahren  allerdings muss hier zunächst die Zahl vom Typ Int in eine Zahl vom Typ Double konvertiert werden, bevor die Konstanten als Fließkommazahlen addiert werden dürfen.


Tipp
Es ist möglich, innerhalb von Texten Berechnungen durchzuführen. Ich empfehle Ihnen, darauf zu verzichten, denn eine solche Konstruktion kann sehr schnell verwirren.
Erzeugen Sie besser eine separate Konstante und fügen Sie diese dann in den Text ein  Sie können sie dann auch geeignet kommentieren!

Das Prinzip der Erzeugung von Texten mit eingebetteten Variablen und Konstanten mag zunächst etwas ungewohnt erscheinen. Das Prinzip ist aber eigentlich ganz einfach: Stellen Sie sich vor, Sie haben die Variablen nummer und fragenText definiert:

let nummer = 5 
let fragenText = "Wieviel ist 2+2?"

Dann haben Sie eine Textverarbeitung, in der Sie den Text

"Frage \(nummer): \(fragenText)"

eingegeben haben. Sie benutzen die »Suchen & Ersetzen«-Funktion, um das \(nummer) durch den Wert von nummer zu ersetzen, also in diesem Fall durch die 5. Dadurch erhalten Sie in Ihrer Textverarbeitung:

"Frage 5: \(fragenText)"

Jetzt benutzen Sie nochmals die »Suchen & Ersetzen«-Funktion, um auch das \(fragenText) durch den Wert von fragenText zu ersetzen, also durch "Wieviel ist 2+2?". Danach steht in Ihrer Textverarbeitung:

"Frage 5: Wieviel ist 2+2?"

Dies ist genau die Funktionsweise der Ersetzung beim Erzeugen von String-Objekten.Wenn Sie daran denken, dass sie eigentlich funktionieren wie die »Suchen & Ersetzen«-Funktion einer Textverarbeitung, so lassen sich auch sehr komplizierte Strings zusammenbauen.

8.2.2. Vergleiche von Strings

Eine häufige Aufgabe besteht darin, einen String zu nehmen und ihn mit einem anderen zu vergleichen. Auf diese Weise können Sie beispielsweise eine Eingabe überprüfen oder bestimmte Wörter suchen und finden. Der Swift-String beherrscht diese Aufgaben von Haus aus.

Für den einfachen Vergleich schreiben Sie ganz normal die doppelten Gleichheitszeichen:

// Erzeuge zwei Strings. 
let ersterString = "abc" 
let zweiterString = "abc"
// Führe den Vergleich durch, das Ergebnis ist hier true. 
ersterString == zweiterString

Das Ergebnis sindGleich ist in diesem Fall true. Das bedeutet, dass beide String-Objekte Zeichen für Zeichen gleich sind.


Für Umsteiger
In Objective-C war es nicht erlaubt, NSString-Objekte auf diesem Weg zu vergleichen. Dort müssen Sie immer eine Vergleichsmethode namens isEqualToString verwenden. Swift hingegen erlaubt Vergleiche für Strings mit den beiden Gleichheitszeichen ==, was äquivalent zu der obigen Methode ist.

Eine etwas anspruchsvollere Aufgabe ist es da schon, Strings ohne Berücksichtigung von Groß- und Kleinschreibung zu vergleichen. Hierfür reichen die doppelten Gleichheitszeichen nicht mehr aus, sondern Sie müssen eine andere Methode verwenden, und zwar localizedCaseInsensitiveCompare. Diese Methode rufen Sie auf dem ersten String auf und übergeben als Parameter den zweiten String. Das Ergebnis ist eine Aufzählung (siehe dazu Abschnitt 7.3). Die drei Fälle sind:
.NSOrderedSame Wenn beide Strings übereinstimmen, bis auf Groß- und Kleinschreibung
.NSOrderedAscending Wenn der erste String im Alphabet vor dem zweiten kommt
(Zeichen werden von links gezählt)
.NSOrderedDescending Das Gegenteil von NSOrderedAscending

Dies ist ein Beispiel für einen Vergleich ohne Berücksichtigung der Groß- und Kleinschreibung:

// Erzeuge zwei Strings. 
let ersterHalloString = "hello" 
let zweiterHalloString = "HeLlO"
// Führe einen zeichenweisen Vergleich durch, dieser ist false. 
ersterHalloString == zweiterHalloString
// Führe einen Vergleich ohne Unterscheidung von Groß-- und 
// Kleinschreibung durch, das Ergebnis ist hier true. 
let vergleich = ersterHalloString.localizedCaseInsensitiveCompare( 
                        zweiterHalloString) 
vergleich == NSComparisonResult.OrderedSame

Das funktioniert nicht nur für deutsche oder englische Texte. Die Methode localizedCaseInsensitiveCompare funktioniert für alle möglichen Sprachen, die vom Betriebssystem unterstützt werden! Abbildung 8.5 zeigt einen Vergleich zweier kyrillischer Strings, die zwar die gleichen Zeichen enthalten, aber unterschiedliche Groß-/Kleinschreibung benutzen. Der Vergleich funktioniert auch mit diesen Zeichen  und auch in anderen Sprachen, die Groß- und Kleinschreibung unterscheiden.

IMG

Abbildung 8.5 Vergleich zweier kyrillischer Strings in Swift mit unterschiedlicher Groß-/Kleinschreibung

In Sprachen, die keine Groß- und Kleinschreibung unterscheiden (wie zum Beispiel Chinesisch oder Arabisch) dürfen Sie diesen Vergleich aber trotzdem benutzen, denn die Methode liefert immer noch einen »normalen« Vergleich in der Form Zeichen für Zeichen.

Vielleicht werden Sie sich fragen, warum Ihre Programme denn Chinesisch oder Arabisch unterstützen sollten. Im Allgemeinen ist es so, dass Benutzer auch in deutschen oder englischen Apps Texte in beliebigen Sprachen eingeben dürfen, auch in Griechisch, Hebräisch oder Hindi. Wenn Sie den Benutzer beispielsweise nach seinem Namen fragen, so könnte es durchaus sein, dass er einen Namen in einem anderen Alphabet eingeben möchte. Dadurch fühlen sich Ihre Benutzer gleich ein bisschen besser aufgehoben. Und mit Swift haben Sie auf Apple-Systemen trotzdem nicht mehr Aufwand!

Dies waren nur einige Beispiel für die Mächtigkeit der objektorientierten Programmierung und der Arbeit, die Sie sich sparen! Wenn Sie selbst sich darum kümmern müssten, alle möglichen Sprachen in Ihren Programmen zu unterstützen, so hätten Sie sehr, sehr viel zu tun!


Tipp
Verwenden Sie die Methoden von NSString so weit wie möglich. Versuchen Sie nicht, Ihre eigenen Methoden für die Aufgaben zu schreiben, die Apple bereits für Sie gelöst hat.

8.2.3. Suchen und Ersetzen

Eine andere Funktion, die Sie möglicherweise öfter brauchen werden, ist das automatische Suchen und Ersetzen. Ich zeige hier mal ein Beispiel, das in der Zukunft wichtig werden könnte: Das automatische Ersetzen des Wortes Euro durch das Wort Drachmen in einem gegebenen Text. Zu diesem Zweck bietet die Klasse String die Methode stringByReplacingOccurrencesOfString. Listing 8.1 zeigt, wie Sie diese Methode auf einen gegebenen String anwenden, um überall das Wort Euro durch das Wort Drachmen zu ersetzen.

// Ein String, der mehrmals das Wort "Euro" enthält. 
let eingabe = "Addieren von 34 Euro und 67 Euro ergibt 101 Euro." 
 
// Ersetzen von "Euro" durch "Drachmen". 
let ausgabe = eingabe.stringByReplacingOccurrencesOfString("Euro", 
                withString: "Drachmen") 
 
// Ausgabe des Ergebnisses: 
// Addieren von 34 Drachmen und 67 Drachmen ergibt 101 Drachmen. 
print(ausgabe)

Listing 8.1 Ersetzen des Wortes »Euro« durch das Wort »Drachmen« in einem gegebenen »String«

Geben Sie diesen Programmtext wieder in das Projekt »Stringspielchen« ein und vergewissern Sie sich, dass das Ergebnis in ausgabe dem Text

Addieren von 34 Drachmen und 67 Drachmen ergibt 101 Drachmen.

entspricht.

8.3 Praxisbeispiel: Schon wieder ein neues Passwort

Sie kennen das Problem wahrscheinlich zu gut: Sie möchten sich irgendwo anmelden und Ihr Lieblings-Nutzername ist schon vergeben  Sie müssen sich mit carola3423481 anmelden. Und dann werden Sie auch noch gezwungen, ein Passwort auszuwählen. Natürlich eines mit mindestens acht Zeichen, das mindestens eine Ziffer enthält und mit keinem anderen Ihrer bisherigen Passwörter übereinstimmt. Und es sollte natürlich auch kein Wort aus einem Buch sein oder irgendetwas, was Sie sich einfach merken können.

Wenn Sie jetzt genervt sind, so habe ich die Lösung für Sie: Schreiben Sie doch einfach Ihren eigenen Passwortgenerator! Um die Sache zunächst schön übersichtlich zu halten, empfehle ich, mit den folgenden Regeln zu beginnen: Ein Passwort soll genau 8 Zeichen lang sein und aus einer Reihe von Ziffern bestehen. Das gesamte Passwort soll am Ende ausgegeben werden und verschiedene Programmläufe sollen unterschiedliche Passwörter liefern.

Legen Sie nun in Xcode ein neues Projekt an, und zwar wieder ein Kommandozeilenprogramm mit dem Namen »PasswortGenerator«. In dieses Projekt nehmen Sie dann den Programmtext auf, den Sie im Folgenden schreiben werden.

8.3.1. Ein einzelnes Zeichen setzen

Um diese Aufgabe zu lösen, müssen Sie einerseits die Zufallszahlen bemühen, die Sie in Abschnitt 6.1.1 kennengelernt haben. Andererseits müssen Sie aber auch sicherstellen, dass Sie nur Zeichen generieren, die Buchstaben oder Zahlen entsprechen. Wenn Sie sich an Abschnitt 4.1.3 erinnern, so werden Zahlen durch eine Folge von einzelnen Zeichen dargestellt. Um also ein achtstelliges Passwort zu erzeugen, so müssen Sie acht verschiedene Zufallszahlen erzeugen, die einem einzelnen Zeichen entsprechen.

Nun kennen Sie bereits String-Objekte  sie entsprechen einer Sammlung von Zeichen, wobei ein einzelnes Zeichen aus einer beliebigen Sprache und einem beliebigen Schreibsystem stammen darf. Um ein zufälliges Passwort zu erzeugen, ist es am zweckmäßigsten, wenn Sie zufällig eine Reihe von Zeichen erzeugen und aus diesen einzelnen Zeichen dann das String-Objekt zusammenbasteln.

Swift hat für einzelne Zeichen einen eigenen Datentyp, den Datentyp Character. Wie Sie Zufallszahlen erzeugen, haben Sie bereits in Abschnitt 6.1.1 gesehen. Ein einzelnes Zeichen können Sie allerdings nicht direkt per Zufallszahl erzeugen  Sie brauchen einen Weg, um eine Zufallszahl in ein Zeichen umzuwandeln. Zu genau diesem Zweck gibt es eine Funktion namens UnicodeScalar. Damit schreiben Sie:

// Die Ziffer ’0’ als einzelnes Zeichen vom Datentyp Character. 
let zifferNull:Character = Character(UnicodeScalar(48))

Die Zahl 48 ist dabei der so genannte ASCII-Wert des Zeichens. ASCII steht für American Standard Code for Information Interchange. Jedes Zeichen entspricht dabei einer anderen Ganzzahl, das heißt, jedes Zeichen hat seinen eigenen ASCII-Wert. Für ausgefallene Schriften wie beispielsweise chinesische Zeichen benötigen Sie eine etwas andere Zahl, den so genannten Unicode-Codepoint. Für weitere Informationen empfehle ich Ihnen, den fortgeschrittenen Abschnitt 8.4.3 gegen Ende dieses Kapitels zu lesen.

Nun ist die Frage, welche Zahlen den gewünschten Zeichen für die Ziffern entsprechen. Die einfachste und beste Möglichkeit ist, Ihren Webbrowser zu bemühen und eine sogenannte ASCII-Tabelle zu finden, zum Beispiel die Tabelle auf Wikipedia: http://de.wikipedia.org/wiki/Ascii.

Für die Ziffern »0« bis »9« sind die Übersetzungen in Tabelle 8.1 gezeigt. Beispielsweise ist dem Zifferzeichen »5« der ASCII-Wert 53 zugeordnet und dem Zifferzeichen »0« der ASCII-Wert 48.

ASCII-Wert Zifferzeichen ASCII-Wert Zifferzeichen
48 0 53 5
49 1 54 6
50 2 55 7
51 3 56 8
52 4 57 9

Tabelle 8.1 ASCII-Werte der Zifferzeichen »0« bis »9«

8.3.2. Lottospielen mit Ziffern

Damit können Sie bereits eine Funktion schreiben, die die geeigneten Zufallszahlen liefert, um einzelne Zeichen für das Passwort zu erzeugen. Die Funktion soll zufallsZifferZeichen heißen, einen Character zurückliefern, der einer Ziffer zwischen »0« bis »9« entspricht und keine Parameter haben. Versuchen Sie es einmal selbst, unter Zuhilfenahme der Funktion zufallsZahl aus Listing 6.1 in Abschnitt 6.1.1 zu schreiben!

Eine mögliche Lösung finden Sie in Listing 8.2 . Wenn Sie nicht exakt die gleiche Funktion geschrieben haben, so ist das kein Problem. Es gibt durchaus mehrere Lösungen und es schadet überhaupt nicht, wenn Sie ein bisschen experimentieren. Sie dürfen übrigens stolz auf sich sein, wenn Sie nicht nur eine einfache Funktion, sondern sogar ein Objekt angelegt und eine geeignete Methode definiert haben!

/// Erzeuge eine Ziffer von ’0’ bis ’9’. 
func zufallsZifferZeichen() --> Character { 
  // Erzeuge einen zufälligen ASCII--Wert einer Ziffer. 
  let zahl = zufallsZahl(48, oben: 57) 
 
  // Wandle den ASCII--Wert in das Zeichen um. 
  let ergebnis = Character(UnicodeScalar(zahl)) 
 
  // Gib es als Ergebnis zurück. 
  return ergebnis 
}

Listing 8.2 Funktion, die eine Ziffer für ein einzelnes Zeichen eines Passworts liefert

Nun brauchen Sie noch eine Funktion, die achtmal die Funktion zufallsZifferZeichen aufruft und die erzeugten Ziffern zu einem String zusammenfügt. Für diese Aufgabe benutzen Sie die Klasse String mit der Methode append. Versuchen Sie wieder, eine entsprechende Funktion namens passwort selbst zu schreiben!

Eine mögliche Lösung zeigt Listing 8.3 . Diese Funktion liefert einen Zeiger auf ein NSString-Objekt zurück. Der String wird durch mehrfache Anwendung von appen zusammengebaut, indem in jedem Durchlauf der Schleife eine neue Ziffer als Character angehängt wird, die von der Funktion zufallsZifferZeichen erzeugt worden ist.

/// Erzeuge ein Passwort bestehend aus 8 zufällig erzeugten 
/// Ziffern. 
func passwort() --> String { 
  // Das anfängliche Passwort ist leer. 
  var passwort = "" 
 
  // Füge 8 einzelne Zifferzeichen hinzu. 
  for var i=1; i<=8; i+=1 { 
    // Hänge ein zufälliges Zifferzeichen an das bestehende 
    // Passwort an. 
    passwort.append(zufallsZifferZeichen()) 
  } 
 
  // Gib das Passwort als Ergebnis zurück. 
  return passwort 
}

Listing 8.3 Funktion, die ein acht Zeichen langes Passwort erzeugt, das aus Ziffern besteht

Um das fertige Programm auszuprobieren, müssen Sie nun Folgendes tun:

1. Wenn Sie es noch nicht getan haben, dann fügen Sie die schon bekannte Funktion zufallsZahl aus Listing 6.1 in Ihr Programm main.swift ein.
2. Vergewissern Sie sich, dass Sie ebenfalls die Funktionen zufallsZifferZeichen aus Listing 8.2 und passwort aus Listing 8.3 eingefügt haben.
3. Um das fertige Programm auszuprobieren, fügen Sie ganz am Ende die in Listing 8.4 gezeigten Zeilen ein.

Starten Sie das Programm mehrmals hintereinander und vergewissern Sie sich, dass jedes Mal neue Zufallszahlen erzeugt werden. Abbildung 8.6 zeigt das Ergebnis eines solchen Durchlaufs.

// Erzeuge ein Passwort. 
let meinPasswort = passwort() 
 
// Gib es auf dem Bildschirm aus. 
print("Neues Passwort: \(meinPasswort)")

Listing 8.4 Hauptteil des Passwortgenerators am Ende der Datei »main.swift«

IMG

Abbildung 8.6 Eine Ausgabe des Programms zum Erzeugung von Passwörtern, die nur aus Ziffern bestehen. Bei mehreren Durchläufen werden jeweils andere Ausgaben erzeugt

8.3.3. Jetzt aber richtig: Großbuchstaben, Kleinbuchstaben und alles zusammen

Was ist nun, wenn Sie stattdessen ein Passwort erzeugen wollen, das sowohl Ziffern als auch Groß- und Kleinbuchstaben enthalten kann? In diesem Fall brauchen Sie eine etwas leistungsfähigere Version der Funktion zufallsZifferZeichen. Und Sie müssen wissen, welche ASCII-Werte denn den Großbuchstaben und welche den Kleinbuchstaben entsprechen. Diese habe ich wieder für Sie zusammengestellt, siehe Tabelle 8.2.

ASCII-Wert Zeichen ASCII-Wert Zeichen
65 A 97 a
66 B 98 b
67 C 99 c
68 D 100 d
69 E 101 e
70 F 102 f
71 G 103 g
72 H 104 h
73 I 105 i
74 J 106 j
75 K 107 k
76 L 108 l
77 M 109 m
78 N 110 n
79 O 111 o
80 P 112 p
81 Q 113 q
82 R 114 r
83 S 115 s
84 T 116 t
85 U 117 u
86 V 118 v
87 W 119 w
88 X 120 x
89 Y 121 y
90 Z 122 z

Tabelle 8.2 ASCII-Werte der Groß- und Kleinbuchstaben

Wie Sie sehen, folgen die ASCII-Werte der Buchstaben einer alphabetischen Reihenfolge. Die Großbuchstaben fangen bei dem ASCII-Wert 65 an (für das große »A«) und gehen bis zu 90 (für das große »Z«), die Kleinbuchstaben fangen bei 97 an (für das kleine »a«) und gehen bis zur 122 (für das kleine »z«).

Um also ein Zeichen zu erzeugen, das entweder eine Ziffer oder ein großer Buchstabe oder ein kleiner Buchstabe ist, müssen Sie einen zufälligen ASCII-Wert erzeugen, der entweder

Versuchen Sie wieder einmal, diese Funktion selbst zu schreiben. Eine mögliche Lösung finden Sie in Listing 8.5 . Wieder gibt es mehrere Möglichkeiten und wenn Ihre Lösung anders aussieht als die hier gezeigte, ist das kein Problem.

/// Erzeuge eine Ziffer von ’0’ bis ’9’ oder einen Groß-- oder 
/// einen Kleinbuchstaben. 
func zufallsZeichen() --> Character { 
  // Dies soll der zufällige ASCII--Wert sein. 
  var asciiWert = 0 
 
  // Die Schleife soll so lange laufen, bis der 
  // ASCII--Wert entweder eine Ziffer, ein Großbuchstabe 
  // oder ein Kleinbuchstabe ist. 
  while (!((asciiWert >= 48) && (asciiWert <= 57)) && 
         !((asciiWert >= 65) && (asciiWert <= 90)) && 
         !((asciiWert >= 97) && (asciiWert <= 122))) { 
    // Erzeugt einen neuen ASCII--Wert. 
    asciiWert = zufallsZahl(48, oben: 122) 
  } 
 
  // Wandle den Wert in ein Zeichen um. 
  let ergebnis = Character(UnicodeScalar(asciiWert)) 
 
  // Gib das Zeichen zurück. 
  return ergebnis 
}

Listing 8.5 Funktion, die ein einzelnes Zeichen eines Passworts erzeugt, entweder eine Ziffer oder einen Groß- oder einen Kleinbuchstaben

Die Bedingung der while-Abfrage sieht kompliziert aus. In der Tat ist es eine Kombination aus drei Abfragen: Die erste prüft, ob zahl zu einer Ziffer passen würde, die zweite, ob es zu einem Großbuchstaben passt, und die dritte, ob es ein Kleinbuchstabe ist. Wenn alle drei Prüfungen fehlschlagen, wird ein neues Zufallszeichen ermittelt. Das geschieht so lange, bis eine der Prüfungen funktioniert  in diesem Fall ist ein passendes Zeichen gefunden!


Hintergrund
Fehler bei Endlosschleifen Vielleicht fragen Sie sich, was denn passiert, wenn zufallsZahl in Listing 8.5 keine gültigen Ergebnisse zurückliefert. In diesem Fall würde die Schleife tatsächlich endlos laufen und das Programm würde »hängen«.
In der Praxis wird das nicht vorkommen, denn ein Zufallszahlengenerator sollte auf jeden Fall Ergebnisse liefern, die früher oder später alle möglichen Zahlen erreichen. Selbst wenn die Schleife dazu ein paar Hundert Mal pro Zeichen aufgerufen werden müsste, so wird das Programm immer noch sehr schnell fertig sein.
Sollte es aber tatsächlich länger laufen, so würde ein Fehler im Zufallszahlengenerator vorliegen. Sie könnten beispielsweise versehentlich nach einer Zufallszahl gefragt haben, die nicht zwischen 48 und 122 liegt, sondern zwischen 48 und 1222222222 (wenn Sie versehentlich eine Taste zu oft gedrückt haben). Dies wäre tatsächlich ein Fehler im Programm und er wäre nur sehr schwer zu finden.
In der Praxis treten diese Arten von Fehlern tatsächlich auf. Es gibt sogar einen eigenen Begriff dafür, die so genannte Endlosschleife. Wenn Sie einmal den Verdacht haben, dass eine Schleife viel länger braucht, als sie eigentlich brauchen dürfte, so vergewissern Sie sich, dass Sie tatsächlich die richtigen Annahmen getroffen haben. In diesem Fall könnten Sie  beispielsweise mit zusätzlichen print-Anweisungen in der Schleife  sich vergewissern, dass der Zufallszahlengenerator tatsächlich sehr schnell ein »gültiges« Zeichen liefert.

Ersetzen Sie nun in der Funktion passwort den Aufruf von zufallsZifferZeichen() durch zufallsZeichen() und lassen Sie das Programm wieder mehrmals laufen. Vergewissern Sie sich, dass es nun Passwörter mit acht Zeichen Länge erzeugt, die sowohl Ziffern als auch Groß- und Kleinbuchstaben enthalten, aber keine anderen Zeichen. Einen Beispiellauf sehen Sie in Abbildung 8.7.

IMG

Abbildung 8.7 Mögliche Ausgabe des Programms zur Erzeugung eines Passworts, das aus Ziffern, Groß- oder Kleinbuchstaben besteht. Wieder sollen erneute Durchläufe jeweils neue Passwörter erzeugen

8.4 Fortgeschrittenes

Die folgenden Abschnitte gehen auf zwei fortgeschrittene Aspekte von Texten ein: die Möglichkeiten, verschiedene Datentypen bei der Ausgabe geeignet zu formatieren und die Art und Weise, wie Ihr Computer Text tatsächlich speichert, nämlich als Folge von Zeichen. Das führt zur so genannten Unicode-Darstellung, mit der Ihr Mac auch Zeichen in anderen Sprachen und Schreibsystemen darstellen kann.

8.4.1. Formatierte Ausgabe

Wenn Sie Zahlen ausgeben, möchten Sie manchmal vorgeben, wieviele Nachkommastellen angezeigt werden sollen, ob führende Nullen aufgefüllt werden sollen oder Sie haben noch andere Wünsche. In Swift existiert für diese Fälle die folgende Schreibweise:

let formatierteAusgabe = String(format: "Zahlen: %.5f, %03i", 3.4, 5)

Diese Form erzeugt einen Text, in dem die Fließkommazahl 3.4 und die Ganzzahl 5 eingebettet sind; erstere wird mit fünf Nachkommastellen und letztere mit 3 Stellen geschrieben, wobei führende Nullen aufgefüllt werden:

Zahlen: 3.40000, 005

Die Dokumentation der Formatstrings finden Sie bei Apple, im Moment aber leider nur mit ihrer Objective-C-Schreibweise und noch nicht mit einer passenden Swift-Form:

8.4.2. Wie Strings gespeichert werden

In Abschnitt 4.4.2 haben Sie erfahren, wie Variablen im Speicher des Computers verwaltet werden. Dies funktioniert nicht nur mit Variablen vom Typ Int, sondern ebenfalls mit allen anderen Datentypen. Objekte vom Typ String benutzen ebenfalls den Speicher, um die einzelnen Zeichen abzulegen. Genau das ist nämlich das Prinzip von Strings: Intern enthalten sie eine Aufreihung einzelner Zeichen!

IMG

Abbildung 8.8 Das Bild des Speichers, in dem ein String die Zeichenfolge »abc« speichert

In Abbildung 4.15 »wohnt« damit jedes einzelne Zeichen an seiner eigenen Adresse und der String ist dafür verantwortlich, dass Texte immer korrekt abgespeichert und zurückgelesen werden können. Abbildung 8.8 zeigt, wie der String

let zeichenFolge = "abc"

intern die Zeichenfolge abc abspeichert. Dabei wird für jedes einzelne Zeichen jeweils ein Byte belegt, vergleiche den fortgeschrittenen Abschnitt 4.4.1. Für die drei Zeichen aus diesem Beispiel findet dabei der ASCII-Wert Verwendung.

Wie aber funktioniert das bei Zeichen in anderen Sprachen? Dafür ist es wichtig, sich mit dem »großen Bruder« der ASCII-Kodierung zu befassen, dem so genanntenUnicode-Zeichensatz.

8.4.3. Unicode

ASCII ist schon sehr alt, es stammt aus dem Jahr 1963. Weil Computer erstmals in Amerika richtig verbreitet waren, haben die Amerikaner die bei ihnen gebräuchlichen lateinischen Schriftzeichen sowie ein paar Sonderzeichen in diese Zuordnung aufgenommen, insgesamt haben sie dabei 128 verschiedene Zeichen benötigt, die Hälfte dessen, was man mit einem Byte darstellen kann, vergleiche Abschnitt 4.4.1. Das hat auch ein paar Jahrzehnte gut funktioniert. Nur irgendwann wollten auch mal Leute außerhalb der USA Computer benutzen und die haben nicht nur nicht Englisch gesprochen, sondern auch noch Zeichen benutzt, die es in der amerikanischen Sprache nicht gibt. Und da waren dann plötzlich die 128 zusätzlichen Zeichen pro Byte nicht mehr genug.

War die deutsche Sprache mit ihren 7 Sonderzeichen (die Umlaute »ä«, »ö« und »ü« jeweils als großes und kleines Zeichen und das ß in nur einer Form) noch recht bescheiden, wurde es mit den griechischen Zeichen anspruchsvoller, mit den kyrillischen recht kompliziert und die chinesischen Zeichen passten nun beim besten Willen nicht mehr in dieses Schema. Eine neue Kodierung musste her!

Die Lösung nennt sich Unicode. Unicode ist eine sehr leistungsfähige Zuordnung, die alle bekannten (und einige weniger bekannte) Sprachen abdeckt. Dank Unicode dürfen Sie Ihre E-Mails nicht nur in traditionellem Chinesisch, in Hebräisch, in Ukrainisch und in Tamil verfassen, sondern Sie dürfen auch in der Sprache der Hochelben oder auf Klingonisch schreiben.

Unicode stellt genauso wie ASCII eine Beziehung zwischen Zeichen und Zahlen her. Allerdings gibt es mehrere verschiedene Möglichkeiten, diese Zahlen im Computer zu speichern. Die schlechte Nachricht ist, dass das sehr kompliziert sein kann. Die gute Nachricht ist, dass Sie sich fast nie direkt damit beschäftigen müssen. Xcode, Cocoa und Ihr Mac nehmen Ihnen sehr viel Arbeit ab.

Dabei benutzt Xcode von sich aus die sogenannte UTF-8-Kodierung. Umgangsprachlich ausgedrückt speichert Ihr Mac Zeichen mit UTF-8 so, dass ASCII-Zeichen noch genauso aussehen wie ASCII-Zeichen (also Sonderzeichen wie das Leerzeichen oder Interpunktionen, große und kleine Buchstaben ohne Umlaute etc.) und für alle anderen Zeichen benutzt er mehrere Bytes. Dies können 2, 3 oder auch bis zu 6 Bytes sein. UTF-8 eignet sich daher gut für Texte in Englisch, Deutsch oder anderen Sprachen mit lateinischen Schriftzeichen. In diesem Buch benutzen alle Beispiele UTF-8. Für eine gute Einführung mit weiteren technischen und historischen Details empfehle ich den folgenden englischsprachigen Einleitungsartikel: http://www.joelonsoftware.com/articles/Unicode.html.

8.5 Aufgaben

1. Der schlaue Steve hat folgende Funktion zur Erzeugung eines Passworts aus Ziffern, Groß- und Kleinbuchstaben geschrieben:
/// Alternative Form von zufallsZeichen. 
func zufallsZeichenVonSteve() --> Character { 
  // Bestimme die Art des Zeichens. 
  let art = zufallsZahl(1, oben: 3) 
 
  // Erzeuge den gewünschten Zeichentyp. 
  var asciiWert:Int 
  switch art { 
  case 1: 
    // Erzeuge eine Ziffer. 
    asciiWert = zufallsZahl(48, oben: 57) 
 
  case 2: 
    // Erzeuge einen Großbuchstaben. 
    asciiWert = zufallsZahl(65, oben: 90) 
 
  default: 
    // Erzeuge einen Kleinbuchstaben. 
    asciiWert = zufallsZahl(97, oben: 122) 
  } 
 
  // Wandle den ASCII--Wert in das Zeichen um. 
  let ergebnis = Character(UnicodeScalar(asciiWert)) 
 
  // Gib dies als Ergebnis zurück. 
  return ergebnis 
}

Steve behauptet, dass »seine« Funktion besser sei als die ursprüngliche aus Listing 8.5 in Abschnitt 8.3.3, denn sie hat kein Problem mit einer Endlosschleife.

Ist das korrekt? Funktioniert diese Funktion genauso wie die alte?

2. Erweitern Sie Listing 8.1 so, dass die Ersetzung nur dann durchgeführt wird, wenn der String den Teilstring griech enthält, und zwar soll Groß- und Kleinschreibung dabei ignoriert werden.

Hinweis: Sie können diese Aufgabe nicht mit den Methoden lösen, die Sie bisher kennen. Stattdessen empfehle ich Ihnen, in der Apple-Dokumentation zu recherchieren, wie Sie das am besten (und vor allem: am einfachsten!) machen können. Diese Aufgabe ist daher nicht einfach!