1. Kodierungen und „Kraukazyabry“
Wenn Sie eine Textdatei lesen, lesen Sie in Wirklichkeit Bytes. Danach wenden Sie eine Regel an, die sagt: „Diese Bytefolge bedeutet genau dieses Zeichen.“ Diese Regel ist die Kodierung. Wenn die Regel falsch gewählt ist, werden Bytes in falsche Zeichen verwandelt — und Sie sehen „Kraukazyabry“. Das ist keine Magie und auch nicht „Swift ist kaputt“, sondern einfach ein falscher Übersetzer.
Eine hilfreiche Analogie: Stellen Sie sich vor, Sie haben einen Satz in lateinischer Schrift aufgeschrieben und versuchen nun, ihn so zu lesen, als wäre es Kyrillisch — oder umgekehrt. Die Buchstaben sind da, aber der Sinn verschwindet. Bei Kodierungen ist es genauso: „Buchstaben“ erscheinen, aber es sind nicht die richtigen.
In unserem Kurs legen wir den Standard fest: Alle unsere Textdateien sind UTF‑8. Nicht, weil UTF‑8 „am richtigsten“ wäre — Kodierungen haben keine Moral —, sondern weil es de facto der Standard für die meisten modernen Systeme und Werkzeuge ist und das gesamte Unicode gut unterstützt.
2. Warum wir im Projekt UTF‑8 wählen
Woher „Kraukazyabry“ kommen
„Kraukazyabry“ entstehen meist nicht in dem Moment, in dem das Programm „schlecht“ ist, sondern wenn der Datenvertrag verletzt wird. Der Vertrag ist einfach: Bytes in Kodierung X geschrieben → müssen auch als X gelesen werden. Wenn in CP1251 geschrieben und als UTF‑8 gelesen wurde, sind Spezialeffekte vorprogrammiert.
Die häufigsten Szenarien:
Erstens: Die Datei wurde von jemandem/etwas nicht in UTF‑8 erstellt, und Sie lesen sie ehrlich als UTF‑8. Dann sind zwei Ergebnisse möglich. Manchmal bricht die Dekodierung einfach mit einem Fehler ab — weil die Bytefolge kein gültiges UTF‑8 ist. Und manchmal „lässt es sich scheinbar lesen“, aber der Text wirkt seltsam — das passiert, wenn Sie mit einer anderen Kodierung lesen, die fast beliebige Bytes „geduldig“ interpretiert.
Zweitens: Sie selbst haben die Datei korrekt geschrieben, aber irgendwo in der Kette wurde sie erneut geöffnet und von einem anderen Werkzeug gespeichert, das die Kodierung „freundlicherweise“ geändert hat — oder im Editor des Benutzers war standardmäßig eine andere Kodierung eingestellt. Am Ende haben sich die Bytes geändert, aber Sie erwarten noch immer das alte Format.
Um den Gedanken zu verankern, hilft eine einfache Skizze:
flowchart LR
A[Datei auf dem Datenträger: Bytes] --> B[Encoding auswählen]
B --> C[In String dekodieren]
C --> D[Text parsen/verarbeiten]
B -. falsches Encoding .-> E[„Kraukazyabry“ oder Dekodierungsfehler]
Und die Kernidee lautet: Das Problem liegt meist im Block B — „falsches Encoding gewählt“ — und nicht in „Parsing“ oder „Swift druckt komisch“.
Warum UTF‑8 für das Projekt praktisch ist
UTF‑8 ist eine Unicode-Kodierung variabler Länge: ASCII-Zeichen belegen 1 Byte, viele „europäische“ Buchstaben oft 2 Bytes, Emojis normalerweise mehr. Das ist ein Kompromiss, der sich hervorragend für „Texte mit viel ASCII“ eignet, und genau das begegnet in Code, Logs und CLI-Ausgaben ständig.
Swift hat sich historisch so entwickelt, dass String die Speicherdetails vor Ihnen verbirgt, aber in Swift 5 und später ist vieles um UTF‑8 als bevorzugte interne Repräsentation optimiert. Das bedeutet nicht, dass Sie jeden Tag in Bytes denken müssen, aber es bedeutet, dass UTF‑8 im Swift-Ökosystem die „natürliche Luft“ ist.
Nützlich ist auch zu wissen, dass es in Foundation viele fertige Varianten von String.Encoding gibt, zum Beispiel .utf8, .isoLatin1, .windowsCP1251 und andere. Das ist ein historisches Erbe aus einer Welt, in der Textdateien lebten, wie sie wollten. Manchmal müssen Sie tatsächlich mit „Nicht-UTF‑8“-Eingaben umgehen — aber das sollte sauber und bewusst geschehen, nicht nach dem Prinzip „wir probieren einfach alles durch“.
3. Praxis: UTF‑8 in Swift schreiben und lesen
Der Idealfall: geschrieben → gelesen
Zuerst sichern wir den Idealfall ab: Wir erstellen die Datei selbst und lesen sie auch selbst. In so einer Welt entstehen „Kraukazyabry“ fast nie — sie haben nichts zu fressen.
Unten sehen Sie einen kleinen Ausschnitt im Stil unseres CLI-Projekts; das kann beispielsweise eine Notizdatei für LibraryCLI sein, etwa note.txt im Datenverzeichnis.
import Foundation
let url = URL(fileURLWithPath: "/tmp/library_note.txt")
let textToSave = "Buch: Swift für Menschen\nStatus: lese ich\n"
do {
try textToSave.write(to: url, atomically: true, encoding: .utf8)
let loaded = try String(contentsOf: url, encoding: .utf8)
print(loaded) // Buch: Swift für Menschen ...
} catch {
print("I/O-Fehler:", error)
}
Hier sind zwei Dinge wichtig. Erstens: encoding wird sowohl beim Schreiben als auch beim Lesen angegeben. Zweitens: Alles ist in do/catch verpackt, denn selbst die ideale Kodierung schützt nicht vor „Datei nicht verfügbar“, „keine Rechte“, „Verzeichnis fehlt“ und anderen irdischen Freuden.
I/O-Fehler vs. Dekodierungsfehler
Eine sehr häufige Verwechslung bei Einsteigern lautet: „Nicht gelesen“ — und ab da ist alles Nebel. Für eine saubere Diagnose ist es aber nützlich, zwei große Problemkategorien zu unterscheiden.
Die erste Kategorie sind I/O-Fehler: Datei existiert nicht, Pfad ist falsch, keine Rechte, Datenträger nicht erreichbar. Die zweite sind Dekodierungsfehler: Die Datei wurde als Bytefolge gelesen, aber diese Bytes lassen sich in der angegebenen Kodierung nicht in Text umwandeln, zum Beispiel weil Sie .utf8 angegeben haben, die Datei aber in einer anderen Kodierung vorliegt und für UTF‑8 ungültige Folgen enthält.
In Swift äußern sich beide Szenarien als throw, aber die Bedeutung ist verschieden. Deshalb ist es im echten Projekt — und auch im Lehr-CLI — sinnvoll, sie wenigstens in der Meldung und in der Frage zu unterscheiden, was Sie als „normalen“ Fall betrachten.
Hier ist eine saubere Funktion zum „streng UTF‑8 lesen“ — ideal für unsere Projektdateien, die wir selbst erzeugen und im Format halten müssen:
import Foundation
func readUTF8Text(from url: URL) -> Result<String, Error> {
do {
let text = try String(contentsOf: url, encoding: .utf8)
return .success(text)
} catch {
return .failure(error)
}
}
Warum Result? Weil das eine praktische Verpackung ist: Man kann das Ergebnis weiterreichen und dort entscheiden, was zu tun ist — dem Benutzer einen Fehler anzeigen, protokollieren, einen Exit-Code zurückgeben und so weiter —, ohne den gesamten Code in eine „do/catch-Leiter“ zu verwandeln.
4. Externe Dateien: Fallback und schnelle Diagnose
Warum es manchmal keinen Fehler, sondern Müll gibt
Hier beginnt das Tückische: Manchmal erhalten Sie bei einer falschen Kodierung keinen throw, sondern „erfolgreich gelesenen Müll“. Und das ist sogar schlimmer als ein Fehler, denn ein Fehler ist wenigstens ehrlich.
Einige Kodierungen sind so gebaut, dass sich fast jedes Byte irgendwie in ein Zeichen umwandeln lässt. Einbyte-Kodierungen können zum Beispiel Werte von 0 bis 255 direkt auf irgendeinen Zeichensatz abbilden. Das Lesen endet dann zwar „erfolgreich“, aber die Zeichenkette sieht wie ein Code aus.
Daraus folgt eine praktische Regel: Wenn Sie eine „fremde“ Datei lesen und eine Fallback-Kodierung wie .isoLatin1 verwenden, müssen Sie verstehen, dass „Erfolg“ noch nicht bedeutet, dass der Text inhaltlich korrekt ist. .isoLatin1 existiert in Foundation als Kodierungsvariante und ist manchmal als letzter Fallback nützlich, maskiert aber leicht ein Problem der Datenquelle.
Kurzer Fallback
Bei unseren eigenen Projektdateien ist ein Fallback normalerweise nicht nötig. Wenn LibraryCLI Daten selbst in UTF‑8 speichert, dann müssen wir beim Lesen UTF‑8 erzwingen. Wenn es doch nicht UTF‑8 ist, dann ist die Datei beschädigt oder jemand hat sie „mit dem falschen Editor“ bearbeitet, und das muss diagnostiziert werden.
Aber manchmal lesen Sie tatsächlich externe Eingaben: Der Benutzer gibt eine Datei vor, die irgendwo erstellt worden sein kann. Dann ist ein Fallback erlaubt, aber er sollte kurz und nachvollziehbar sein. Kein „wir probieren zwanzig Kodierungen und hoffen einfach“.
Unten sehen Sie ein Beispiel für einen engen Fallback: zuerst UTF‑8 als Standard, dann eine weitere Variante, etwa Windows-1251 als verbreitete historische Kodierung für Kyrillisch; sie existiert ebenfalls in String.Encoding.
import Foundation
func readTextWithFallback(from url: URL) -> (text: String, encoding: String.Encoding)? {
if let t = try? String(contentsOf: url, encoding: .utf8) {
return (t, .utf8)
}
if let t = try? String(contentsOf: url, encoding: .windowsCP1251) {
return (t, .windowsCP1251)
}
return nil
}
Achten Sie darauf: Hier wird try? bewusst eingesetzt. Wir verstecken den Fehler nicht für immer, sondern verwenden try? als Hypothesenprüfung: „Ist es vielleicht UTF‑8? Nein? Dann vielleicht CP1251?“ Wenn beide Hypothesen scheitern, geben wir nil zurück, und der aufrufende Code entscheidet dann, was dem Benutzer gesagt wird.
Wenn Sie eine noch ehrlichere Variante möchten, können Sie Result mit einem eigenen Fehler zurückgeben, um zwischen „Datei überhaupt nicht gelesen“ und „gelesen, aber Kodierung unbekannt“ zu unterscheiden. Aber selbst ein einfaches nil ist schon besser als try! und ein Programmabsturz.
Kleine Diagnose vor Ort
In CLI-Anwendungen möchte man oft schnell verstehen: „Ist das wirklich Text?“, „Sieht er aus wie UTF‑8?“, „Warum sieht der Benutzer Kraukazyabry?“ Wir gehen noch nicht zur vollständigen Arbeit mit Data und Bytes als primärem Objekt über — das kommt an einem anderen Tag —, aber schon auf String-Ebene kann man ein paar einfache Beobachtungen machen.
Erstens: Wenn Sie Kyrillisch erwarten, aber viele Zeichen wie Ð und Ñ erhalten, ist das ein klassisches Symptom: UTF‑8-Bytes wurden als einbyteige lateinische Kodierung gelesen — oder umgekehrt. Zweitens: Man kann sich ansehen, wie die Zeichenkette als UTF‑8-Bytes aussieht — schon allein, um zu verstehen, dass sie überhaupt nicht leer ist und Nicht-ASCII enthält.
Hier ist ein kleines Beispiel, eher diagnostisch als „produktionsreif“:
import Foundation
let sample = "Hello"
let bytes = Array(sample.utf8)
print(sample) // Hello
print(bytes.count) // 12 (Cyrillic takes > 1 byte in UTF-8)
Das löst das Problem nicht automatisch, hilft aber dabei, eine wichtige Gewohnheit zu entwickeln: Text sind nicht nur „Buchstaben auf dem Bildschirm“, sondern auch eine Byte-Repräsentation nach einer bestimmten Regel.
5. Spickzettel: Symptome und Maßnahmen
Unten ist ein praxisnaher Spickzettel. Er macht Sie nicht in 30 Sekunden zum Kodierungs-Experten, hilft aber schnell dabei, die Richtung zu wählen: Wo reparieren wir — beim Lesen, beim Schreiben oder im Formatvertrag?
| Symptom im Programm | Was das oft bedeutet | Was wir im Kurs tun |
|---|---|---|
|
Datei ist nicht UTF‑8 oder die Datei wurde nicht gelesen (Rechte/Pfad) | Diagnose trennen: Pfad/Rechte prüfen, dann davon ausgehen, dass die Datei nicht im Projektformat vorliegt |
Der Text wurde „gelesen“, sieht aber aus wie |
UTF‑8-Bytes wurden als andere Kodierung interpretiert | Vertrag festlegen: Unsere Dateien sind immer UTF‑8; für externe Dateien machen wir einen kurzen Fallback |
| „Kraukazyabry“ erscheinen nur bei einem Benutzer | Die Datei wurde vom Editor des Benutzers in einer anderen Kodierung gespeichert | Bitten, die Datei als UTF‑8 zu speichern, oder die externe Datei mit Fallback und Warnung lesen |
| Alles ist in der Datei korrekt, aber die Terminalausgabe ist seltsam | Selten, aber möglich: Umgebung/Terminal ist ungewöhnlich konfiguriert | Im Kurs gehen wir davon aus, dass das Terminal UTF‑8 ist; wenn nicht, muss der Benutzer die Umgebung normalisieren |
Diese Tabelle ist absichtlich „erdnah“: Wir versuchen nicht, alles auf der Welt zu erraten, sondern definieren vorhersagbares Verhalten unserer Anwendung und klare Regeln für externe Eingaben.
6. Typische Fehler
Bei diesem Thema sind die Fehler besonders tückisch, weil „es funktioniert ja irgendwie“: Die Datei wird gelesen, die Zeichenkette ist da, das Programm stürzt nicht ab — aber der Sinn des Textes ist bereits verloren. Deshalb ist es wichtig, Probleme über Gewohnheiten zu vermeiden: Kodierung immer explizit angeben, try? nicht zu einer Methode machen, „nichts zu behandeln“, und keine endlosen Fallback-Ketten bauen, die die Fehlerquelle verschleiern.
Fehler Nr. 1: encoding nicht angeben und hoffen, dass es „von selbst erraten“ wird.
Wenn die Kodierung nicht explizit festgelegt ist, überlassen Sie die Kontrolle der Umgebung und dem Zufall. An einer Stelle kann das funktionieren, an einer anderen zu „Kraukazyabry“ führen, und Sie suchen lange nach dem Punkt, an dem Swift alles verdorben haben soll. Im Kurs ist die richtige Gewohnheit einfach: Beim Lesen und Schreiben von Text immer .utf8 angeben.
Fehler Nr. 2: try! beim Lesen/Schreiben von Text verwenden.
try! ist kein „schneller Weg“, sondern ein Versprechen an den Compiler: „Ich garantiere, dass kein Fehler auftritt.“ Bei Dateien ist das fast immer falsch: Die Datei kann gelöscht werden, Rechte können sich ändern, der Datenträger kann nicht verfügbar sein. Das Ergebnis ist ein Programmabsturz genau dort, wo man dem Benutzer ruhig eine Meldung zeigen und sauber beenden könnte.
Fehler Nr. 3: try? als „universellen Hirnausschalter“ verwenden.
try? ist gut als „Hypothesenprüfung“ — zum Beispiel in einem kurzen Lese-Fallback —, aber schlecht als Methode, Fehler beim Schreiben oder Lesen wichtiger Daten wegzuwerfen. Wenn Sie let text = try? ... schreiben und danach still mit einem leeren String arbeiten, verlieren Sie die Ursache des Problems und machen sich die Diagnose zehnmal schwerer.
Fehler Nr. 4: einen Fallback „für alle Kodierungen der Welt“ bauen und das für Zuverlässigkeit halten.
Eine lange Kette mit ?? und Kodierungsdurchlauf verwandelt das Programm in einen Wahrsager. Manchmal errät es die falsche Kodierung, liefert aber trotzdem einen String — und Sie erhalten eine stille Datenkorruption. Im Kurs halten wir uns an Disziplin: Für eigene Dateien strikt UTF‑8, für externe Dateien eine kurze Liste von Fallback-Varianten und eine klare Meldung, dass das Format unerwartet war.
Fehler Nr. 5: „Datei lesen“ und „Format parsen“ in einem Schritt vermischen.
Wenn Lesen, Dekodieren, Zerlegen von Zeilen und Typumwandlung in einer Funktion zusammengeworfen werden, werden „Kraukazyabry“ zum Albtraum: Sie verstehen nicht mehr, ob das Problem in der Kodierung, in den Trennzeichen, in leeren Zeilen oder in falschen Zahlen liegt. Viel einfacher ist es, zuerst einen String zu erhalten — oder einen verständlichen Fehler — und erst danach das Format zu analysieren.
GO TO FULL VERSION