Haben Sie mit dem Base64-Format zu tun? Dann ist diese Website genau das Richtige für Sie! Nutzen Sie unser superpraktisches Online-Tool, um Ihre Daten zu kodieren oder zu dekodieren.

Base64-Kodierung in Swift: Ein vollständiger Leitfaden

Sie haben etwas, das reisen muss, und der Weg ist nur Text: eine JSON-API, die rohe Bytes ablehnt, ein E-Mail-Kanal, der sich an seine 7-Bit-Ursprünge erinnert, eine URL, die bei allem, was sie nicht benennen kann, den Lade hebt, und eine Konfigurationsdatei, die nur die schlichtesten Zeichen annimmt. Willkommen auf der Packseite von base64, wo Swift Ihre Bytes mit einem Methodenaufruf in eine freundliche Mauer aus Buchstaben verwandelt, mit einem Zuschlag von grob einem Extra-Zeichen pro drei Bytes und ein paar Umbruch-Optionen, die existieren, weil zwei verschiedene Jahrzehnte Meinungen über Zeilenlängen hatten.

Die Startseite dieser Seite erklärt das Format bereits im Detail (64 druckbare Zeichen, vier davon pro drei Eingabe-Bytes, bis zu zwei =-Zeichen Padding auf der letzten Gruppe), also ist die Formatschulstunde vorbei, bevor sie beginnt. Zwei Fakten für diesen Artikel: base64 ist Packen, kein Versperren, und das Packen vergrößert Ihre Daten um etwa 33 Prozent, was jedes Mal etwas ausmacht, wenn Sie in der Nähe eines Größenlimits sind. In Swift läuft der ganze Job durch einen Typ, Data, und eine totale Methode, base64EncodedString(options:). Die einzige echte Fähigkeit, die benötigt wird, ist zu wissen, was in den beiden Schritten um diese Methode herum passiert, denn die Methode selbst scheitert nie. Es sind die Schritte, die es tun.

Eine Methode, null Ausreden

Alles base64-Bezogene in Swift lebt auf Data aus dem Foundation-Framework, und es lebt dort seit den ersten Releases der Sprache (Apple listet die Methode ab iOS 8.0, macOS 10.10, tvOS 9.0, watchOS 2.0 und visionOS 1.0). Die Pipeline sind immer dieselben drei Schritte: Bringen Sie Ihren Inhalt in ein Data, rufen Sie die Methode auf, liefern Sie den String aus.

import Foundation

let note = "Pack it, wrap it, ship it."
let packed = Data(note.utf8).base64EncodedString()
print(packed) // UGFjayBpdCwgd3JhcCBpdCwgc2hpcCBpdC4=

Zwei Details in diesen drei Zeilen verdienen einen genaueren Blick. Erstens ist Data(note.utf8) der stille Schritt: Die utf8-Sicht kann per Definition jeden Unicode-Skalar darstellen, also scheitert sie nie, weshalb sie in den meisten Beispielen der Standard ist. Die failable Cousine, note.data(using:), kann und gibt tatsächlich für einige Kodierungen nil zurück, und die ganze "welche Bytes"-Entscheidung bekommt unten ihren eigenen Abschnitt, denn es ist der erste Ort, an dem Ihre Daten abhandenkommen können. Zweitens ist die Methode selbst total: Sie antwortet immer, hat keinen Fehlerfall, und die einzige Frage, die sie Ihnen stellt, ist, welchen Zeilenumbruch Sie wollen. Es gibt auch eine Geschwistermethode, base64EncodedData(options:), die das verpackte Ergebnis als Data aus ASCII-Bytes zurückgibt statt als String, für Pipelines, bei denen der nächste Halt eine binäre API ist statt ein Textfeld.

Und weil die Hälfte von Ihnen über "meine Swift-App braucht eine base64-Abhängigkeit" hierhergekommen ist: Es gibt nichts zu installieren. Base64 ist Teil von Foundation, Foundation ist Teil der Toolchain, und die Toolchain kommt auf jeder Plattform auf demselben Weg an. Auf macOS ist es Xcode oder die Kommandozeilen-Tools; auf Linux und Windows ist es der Installer von swift.org, wo die aktuelle stabile Linie, Stand jetzt, 6.3.x ist und der Swiftly-Versionsmanager die empfohlene Eingangstür; und offizielle Docker-Images decken die Container-Leute ab. Ihr Package.swift bleibt leer, und es sollte.

Die erste echte Entscheidung: Welche Bytes?

Bevor ein einziges base64-Zeichen produziert wird, haben Sie bereits die Entscheidung getroffen, die am meisten zählt, denn base64 packt Bytes, und ein String ist nur ein String, bis Sie seine Byte-Form wählen. UTF-8 ist der gesunde Standard und die richtige Antwort für fast alles, aber im Moment, in dem Ihre Daten aus einem Legacy-System, einem binären Protokoll oder einer Ecke von Unicode kommen, wird die Wahl nicht mehr unsichtbar:

import Foundation

let phrase = "héllo"
print(phrase.data(using: .utf8)?.count ?? -1)              // 6
print(phrase.data(using: .ascii) == nil)                   // true
print(phrase.data(using: .utf16)?.count ?? -1)             // 12
print(phrase.data(using: .utf16LittleEndian)?.count ?? -1) // 10
print(phrase.data(using: .utf8)!.base64EncodedString())
// aMOpbGxv
print(phrase.data(using: .utf16LittleEndian)!.base64EncodedString())
// aADpAGwAbABvAA==
Umwandlung Bytes für "héllo" Was das base64 trägt
.utf8 6 aMOpbGxv, die Schreibweise, die moderne APIs erwarten
.ascii scheitert mit nil das akzentuierte Zeichen liegt über 0x7F, und ASCII lehnt es ab
.utf16 12 das Doppelte der UTF-8-Größe, plus eine zweibyte Byte-Order-Mark (BOM), die an der Spitze mitfährt
.utf16LittleEndian 10 dasselbe Wort ohne das BOM-Label: 10 Bytes, immer noch das Schwerste der hier gelisteten BOM-freien Optionen

Drei Lektionen stecken in diesem Output. Die data(using:)-Form ist failable, und .ascii ist ein Prime-Kandidat zu scheitern, also ist der Force-unwrap der Weg, wie aus einem perfekten Satz eine abgestürzte App wird. Die einfache .utf16-Umwandlung hängt eine zweibyte Byte-Order-Mark (BOM) vorweg (auf einer Little-Endian-Maschine FF FE), und dieser BOM reist in Ihr verpacktes Ergebnis und verwirrt jeden Dekodierer, der ihn nicht erwartet hat. Und die Größenrechnung ist unbarmherzig: Eine nachlässige Zeichensatz-Wahl kostet Sie den base64-Zuschlag auf das Doppelte der Daten, also ist die Frage nie "wird das kodiert?", sondern "was wird das andere Ende erwarten, wenn es auspackt?". Die goldene Regel: Beide Enden der Reise müssen sich vor Beginn des base64 auf die Byte-Form einigen, denn der Dekodierer hat keine Möglichkeit, zu raten, was Sie gewählt haben, und er wird auch nicht fragen.

Umbruch: Zwei Gewohnheiten, ein Parameter

Die Optionen der Methode drehen sich allesamt um Zeilenumbrüche, und alle existieren, weil zwei Formate des 20. Jahrhunderts sich nicht einigen konnten, wie lang eine Zeile aus Buchstaben sein sollte. MIME, der E-Mail-Standard von 1996, bricht base64 bei 76 Zeichen mit CRLF-Zeilenenden um. PEM, die Privacy-Enhanced-Mail-Linie von 1987, bricht bei 64 Zeichen um, und genau diese Form finden Sie in Zertifikaten und Schlüsseln, in den -----BEGIN CERTIFICATE------Blöcken, die Ihre Server in einem Verzeichnis für Einstellungen aufbewahren.

import Foundation

let certBytes = Data((0..<300).map { UInt8($0 % 256) })
let raw = certBytes.base64EncodedString()
let pemStyle = certBytes.base64EncodedString(options: [.lineLength64Characters, .endLineWithLineFeed])
let mimeStyle = certBytes.base64EncodedString(options: [.lineLength76Characters,
  .endLineWithCarriageReturn, .endLineWithLineFeed])
print(raw.count)                                        // 400 Zeichen auf einer Zeile
print(pemStyle.components(separatedBy: "\n").count)    // 7 Zeilen mit höchstens 64
print(mimeStyle.components(separatedBy: "\r\n").count) // 6 Zeilen mit höchstens 76
Option Job Achten Sie auf
.lineLength64Characters bricht eine Zeile nach 64 Zeichen, die PEM-Gewohnheit das Zeilenende ist CRLF, solange Sie nicht etwas anderes sagen
.lineLength76Characters bricht eine Zeile nach 76 Zeichen, die MIME-Gewohnheit dasselbe CRLF als Standard
.endLineWithCarriageReturn schließt einen Wagenrücklauf in das Zeilenende ein für sich allein ist das nur CR, im alten Mac-Stil, und selten, was Sie wollen
.endLineWithLineFeed schließt einen Zeilenvorschub in das Zeilenende ein geben Sie beide Optionen an, wenn Sie CRLF meinen

Nun der Standard, der die Leute überrascht: Fordern Sie irgendeine .lineLength-Option an, ohne ein Zeilenende zu wählen, ist das Zeilenende, das Sie erhalten, CRLF, das volle Paar aus Wagenrücklauf und Zeilenvorschub. Die Methode hat einen Hausstil, und ihr Hausstil ist 1996. Sie wollen nur LF? Bezahlen Sie das explizit mit .endLineWithLineFeed und nichts anderem. Noch eine Hausregel für die Akten: Die letzte Zeile bekommt niemals ein Zeilenende am Schluss. Ein umgebrochenes Ergebnis endet mit seinem letzten Datenzeichen oder seinen =-Pads, ganz egal, welche Optionen Sie gewählt haben, so können Sie verketten und einfügen, ohne eine verwitwete Leerzeile am Ende. Und ganz ohne Optionen ist der Output eine einzige ununterbrochene Zeile, die richtige Form für JSON-Bodies, URLs und API-Payloads: die Arbeit, die eine moderne Swift-App die meiste Zeit tatsächlich leistet.

Base64url: Ein String, der reisen kann

Das Standardalphabet ist ein vorzeigbarer Bürger in JSON und ein schlimmer Bürger in einer URL. In einem Query-String wird ein + beim Form-Parsing als Leerzeichen gelesen, ein / ist ein Pfadtrenner, und ein = trennt Schlüssel von Werten, deshalb macht die Prozent-Kodierung des Standardalphabets es länger und hässlicher statt kürzer. Abschnitt 5 von RFC 4648 existiert, um genau das zu beheben: das "URL- und Dateinamen-sichere Alphabet", in dem + zu - wird, / zu _ und das =-Padding meist weggelassen wird, weil ein Pad in einer URL typischerweise zu %3D wird und damit den Sinn zunichtemacht. Der RFC fügt eine Warnung hinzu, die gerahmt werden sollte: Diese Kodierung "sollte nicht als dasselbe wie die base64-Kodierung betrachtet werden". YouTube-Video-IDs, JWTs und die meisten modernen API-Kennungen sprechen es, also rechnen Sie damit, es zu verwenden.

import Foundation

extension Data {
  var base64URLEncoded: String {
    base64EncodedString()
      .replacingOccurrences(of: "+", with: "-")
      .replacingOccurrences(of: "/", with: "_")
      .replacingOccurrences(of: "=", with: "")
  }
}

let tricky = Data("The + / and = trio goes home.".utf8)
print(tricky.base64EncodedString())
// VGhlICsgLyBhbmQgPSB0cmlvIGdvZXMgaG9tZS4=
print(tricky.base64URLEncoded)
// VGhlICsgLyBhbmQgPSB0cmlvIGdvZXMgaG9tZS4

Schauen Sie sich diesen Output genau an: Dieser besondere Payload hat zufällig kein + und kein / produziert, also unterscheiden sich die beiden Schreibweisen nur um das weggelassene Padding. Ändern Sie ein Byte, und sie werden sich im Alphabet trennen, was der ganze Punkt ist. Zwei Verhaltensregeln. Wählen Sie den Dialekt einmal, an der Grenze, an der Ihre Daten die Außenwelt treffen, und mischen Sie niemals Alphabete innerhalb desselben Dokuments: Ein standardmäßiger Dekodierer, der base64url erhält (oder umgekehrt), wird die Eingabe entweder verwerfen oder, in laxen Modi, die fremden Zeichen löschen und Ihnen falsche Bytes aushändigen. Und nennen Sie Ihren Helfer ehrlich, damit der nächste Entwickler weiß, dass der String base64url ist und kein Tippfehler. Dieselbe Extension kann auf neueren Toolchains kürzer werden: Die neuesten SDK-Betas enthalten jetzt eine native .base64URLAlphabet-Option, die den Alphabet-Tausch innerhalb des Frameworks erledigt, dazu eine passende .omitPaddingCharacter-Option, und die Open-Source-Foundation trägt dieselben Optionen hinter einem Verfügbarkeitsmarker für spätere Toolchains. Bis diese Ihr Mindest-Deployment-Ziel erreichen, ist die vierzeilige Extension die portable Antwort, und sie wird nach Konstruktion auf jeder Plattform weiter funktionieren.

JSON und APIs: Das Base64, um das Sie nie gebeten haben

Überrascht die meisten Leute, die mit Codable arbeiten, am meisten, also bekommt es seinen eigenen Abschnitt: Die Standardstrategie von JSONEncoder für eine Data-Eigenschaft ist bereits base64. Wenn ein Codable-Struct ein Data-Feld hat, packt der Encoder es automatisch mit Standard-base64, und JSONDecoder packt es auf dem Rückweg automatisch wieder aus. Keine Option, keine Konfiguration, keine Zeremonie.

import Foundation

struct Snapshot: Codable {
  let name: String
  let icon: Data
}

let snap = Snapshot(name: "cat", icon: Data("🐱".utf8))
let json = try JSONEncoder().encode(snap)
print(String(decoding: json, as: UTF8.self))
// das Icon ist über die Leitung als "8J+QsQ==" gegangen

Die icon-Eigenschaft ist über die Leitung als 8J+QsQ== gegangen, denn das ist der Hausstil. Es gibt Alternativen, und die zwei, auf die Sie tatsächlich treffen werden, sind .custom, das Ihnen die Daten und einen Encoder in die Hand gibt und Ihnen die Darstellung überlässt, und das neuere .deferredToData, das an die Dateninstanz selbst delegiert. Im Moment, in dem eine API base64url statt Standard will, ist .custom der Ort, an dem Ihre Extension aus dem vorherigen Abschnitt angeschlossen wird:

import Foundation

extension Data {
  var base64URLEncoded: String {
    base64EncodedString()
      .replacingOccurrences(of: "+", with: "-")
      .replacingOccurrences(of: "/", with: "_")
      .replacingOccurrences(of: "=", with: "")
  }
}

struct Snapshot: Codable {
  let name: String
  let icon: Data
}

let encoder = JSONEncoder()
encoder.dataEncodingStrategy = .custom { data, enc in
  var container = enc.singleValueContainer()
  try container.encode(data.base64URLEncoded)
}
let json = try encoder.encode(Snapshot(name: "cat", icon: Data("🐱".utf8)))
print(String(decoding: json, as: UTF8.self))
// das Icon ist über die Leitung als "8J-QsQ" gegangen

Eine Warnung, die eine funktionierende Funktion von einem Produktionsvorfall trennt: Ein JSON-String darf keinen rohen Zeilenumbruch enthalten. Wenn Sie einen Payload mit einer .lineLength-Option umbrechen und das Ergebnis unescapet in ein JSON-Dokument einbauen, haben Sie gar keinen JSON-Wert erzeugt; Sie haben einen Syntaxfehler mit base64-Akzent erzeugt, und der Parser wird es beweisen. Umgebrochener Output gehört in E-Mail-Bodies und Zertifikatsdateien. Alles, was in JSON, URLs oder Query-Strings lebt, bekommt den einfachen, nicht umgebrochenen String.

Data URIs: Das Bild in einem String

Der Lieblingstrick des Webs ist es, die Bytes einer Datei direkt in eine URL einzubetten: data:{mime};base64,{payload}. Einen in Swift zu bauen ist ein Lesen, ein Kodieren und eine String-Konkatenation:

import Foundation

let gif = Data(base64Encoded: "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")!
let uri = "data:image/gif;base64," + gif.base64EncodedString()
print(uri.hasPrefix("data:image/gif;base64,R0lGODlh")) // true
print(gif.count) // 42

Das Beispiel baut das berühmte transparente GIF mit 42 Bytes (kleinere, nicht transparente gibt es, aber dies ist das, das jeder einbettet) zu einer Data URI auf, die ein Browser ohne zweite Anfrage rendert. Auf Apple-Plattformen ist die umgekehrte Richtung ein One-Liner: Das gleiche Data, das Sie verpackt haben, füttert direkt UIImage(data:) oder NSImage(data:). Der Kompromiss ist die Größe, und sie addiert sich: Ein 100-Kilobyte-Bild wird zu einem String mit über 133.000 Zeichen, noch bevor Sie das data:image/png;base64,-Präfix hinzufügen. Data URIs glänzen bei Icons, Avataren und winzigen Assets, und sie lassen Bandbreite bei Heldenfotos still und leise anschwellen, also behalten Sie sie für die kleinen Dinge.

JWTs: Die ersten beiden Teile versiegeln

Die Kodierungsseite eines JSON Web Tokens sind zwei Versiegelungen plus eine Signatur, und die Versiegelung ist Ihre base64url-Extension mit weggelassenem Padding, genau das, was das Format verlangt. Der Header und der Payload sind JSON-Dokumente, und beide Teile bekommen dieselbe Behandlung:

import Foundation

extension Data {
  var base64URLEncoded: String {
    base64EncodedString()
      .replacingOccurrences(of: "+", with: "-")
      .replacingOccurrences(of: "/", with: "_")
      .replacingOccurrences(of: "=", with: "")
  }
}

func seal(_ text: String) -> String {
  Data(text.utf8).base64URLEncoded
}

let header = seal(#"{"alg":"HS256","typ":"JWT"}"#)
let claims = seal(#"{"sub":"42","role":"editor"}"#)
print("\(header).\(claims).signature-here")
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiIsInJvbGUiOiJlZGl0b3IifQ.signature-here

Zwei Erinnerungen. Der dritte, durch Punkte getrennte Teil ist eine kryptografische Signatur, berechnet über die ersten beiden, und er ist der einzige Teil des Tokens, der irgendeine Garantie liefert: Der Header und die Claims sind nacktes JSON in einem Trenchcoat, also gehören Geheimnisse niemals hinein. Und beachten Sie, wie das Padding in seal() verschwindet: Die JWT-Dekodierer auf der anderen Seite (einschließlich des aus dem Schwester-Artikel) stellen es mit einer Modulo-Ergänzung wieder her, so dass sich die beiden Richtungen der Reise auf gemeinsamem Boden treffen.

HTTP-Header: Basic und der Rest

Der alte Authorization: Basic-Header will einen Benutzernamen und ein Passwort, verbunden durch einen Doppelpunkt, verpackt mit Standard-base64, denn in einem Header sind + und / harmlos, und die Dialekt-Frage stellt sich nicht:

import Foundation

let credentials = "editor:s3cret"
let header = "Basic " + Data(credentials.utf8).base64EncodedString()
print("Authorization: " + header)
// Authorization: Basic ZWRpdG9yOnMzY3JldA==

Dieselbe laute Fußnote wie überall: Das Verpacken liefert von selbst null Sicherheit, und der Header ist nur so sicher wie die HTTPS-Verbindung, die ihn trägt. Der moderne Cousin, Authorization: Bearer, trägt ein JWT, also ist es das Versiegelungs-Rezept aus dem JWT-Abschnitt, das dort auf die Leitung kommt. Der eine Ort, an dem sich die Dialekt-Frage in HTTP tatsächlich stellt, ist der Query-String: Wenn Ihre API eine Kennung in einer URL fahren lässt, sollte diese Kennung base64url sein, oder zumindest Standard-base64 mit Prozent-Kodierung, niemals das rohe Standardalphabet, dessen + in Ruhe als Leerzeichen gelesen wird.

E-Mail-Anhänge: Der 76-Zeichen-Vertrag

Wenn Ihre App einen Anhang erzeugt, der die 7-Bit-Ursprünge von SMTP überleben muss, ist der Vertrag der von MIME: base64, umgebrochen bei 76 Zeichen mit CRLF-Zeilenenden, und ein Content-Transfer-Encoding: base64-Header, der dem Empfänger sagt, was ihn erwartet. Der Options-Abschnitt hat die Schreibweise bereits gezeigt; hier ist die vollständige Form eines umgebrochenen Bodies:

import Foundation

let attachment = Data((0..<400).map { UInt8(65 + $0 % 26) })
let body = attachment.base64EncodedString(options: [.lineLength76Characters,
  .endLineWithCarriageReturn, .endLineWithLineFeed])
let lines = body.components(separatedBy: "\r\n")
print(lines.count)                     // 8 Zeilen
print(lines.map { $0.count }.max() ?? 0) // 76, die längste
print(body.hasSuffix("\r\n"))           // false, die letzte Zeile bleibt ohne

Die Größenrechnung für diesen Dialekt ist die berühmte: Die 4/3-Alphabet-Steuer plus ein Zeilenumbruch alle 76 Zeichen landet nahe bei 137 Prozent des Originals, und der alte Mail-Engineering-Kurzweg "das Original mit 1,37 multiplizieren und etwa 800 Bytes Header dazurechnen" funktioniert immer noch, um Anhanggrößen in einem Mail-Client im Auge zu behalten. Es ist Folklore mit korrekter Arithmetik, und es ist der eine Ort in diesem Artikel, an dem der 33-Prozent-Zuschlag eine zweite Nachkommastelle bekommt.

Konfiguration, Umgebung und Datenbanken: Den Unterstrich verstecken

Es gibt eine stille Klasse von Jobs, bei der base64s einzige Tugend ist, dass sein Output ein kleiner, vorhersehbarer Zeichensatz ist: einen binären Blob oder einen strukturierten Wert an einem Ort unterzubringen, der plain Text will. Umgebungsvariablen, die eine Shell-Konfigurationsdatei überleben müssen, Spalten in einer Datenbank, die mit varchar glücklicher ist als mit blob, eine LDAP-Datei mit ihrem base64-Marker, ein QR-Code, der Buchstaben zuverlässiger scannt als Bits. Das Muster ist überall dasselbe: die Bytes entscheiden, kodieren, den String speichern, auf der anderen Seite dekodieren.

import Foundation

struct FeatureFlags: Codable {
  var betaToolbar: Bool
  var maxRetries: Int
}

do {
  let flags = FeatureFlags(betaToolbar: true, maxRetries: 5)
  let json = try JSONEncoder().encode(flags)
  let storable = json.base64EncodedString()
  print(storable)
  guard let packed = Data(base64Encoded: storable) else {
    print("decode failed, that is odd")
    exit(1)
  }
  let restored = try JSONDecoder().decode(FeatureFlags.self, from: packed)
  print(restored.betaToolbar, restored.maxRetries)
} catch {
  print(error)
}

Zwei Fallgruben leben hier. Die erste ist die doppelte Umhüllung: zwei Integrations-Ebenen, die beide "hilfsbereit" kodieren, so dass der gespeicherte Wert base64 von base64 ist, und der Leser, der einmal dekodiert, bekommt eine Mauer aus Buchstaben und denkt, die Funktion sei kaputt. Kodieren Sie genau einmal, an genau einer Grenze, und sagen Sie es in einem Kommentar. Die zweite ist Dialekt-Drift durch die Umgebung: Wenn der Wert jemals durch eine URL, ein Formularfeld oder eine Shell fahren wird, die + und / verunstaltet, speichern Sie stattdessen die base64url-Schreibweise, denn der Zeichensatz ist der ganze Punkt des Formats.

Dateien: Die .b64-Rundreise

Der Job "diese Datei in eine .b64-Textdatei verwandeln" ist ein Lesen, ein Aufruf und ein Schreiben:

import Foundation

let source = URL(fileURLWithPath: "photos/cat.png")
let archive = URL(fileURLWithPath: "photos/cat.b64")
let bytes = try Data(contentsOf: source)
try Data(bytes.base64EncodedString().utf8).write(to: archive)

// später, möglicherweise in einem anderen Prozess
let packed = try String(contentsOf: archive, encoding: .utf8)
let restored = Data(base64Encoded:
  packed.trimmingCharacters(in: .whitespacesAndNewlines))
if let restored = restored {
  try restored.write(to: URL(fileURLWithPath: "photos/cat-copy.png"))
} else {
  print("the .b64 file was not base64 after all")
}

Das trimmingCharacters auf der Rückreise ist da, weil was auch immer die Datei geschrieben hat, vielleicht ein Zeilenende hinzugefügt hat, und der strenge Dekodierer einen Zeilenumbruch am Ende als Urteil nil behandelt. Diese Rundreise kommt Byte für Byte zurück, was Sie beim ersten Ausliefern verifizieren sollten. Für Dateien, die groß genug sind, damit der Speicherverbrauch interessant wird, kodieren Sie nicht den ganzen Puffer auf einmal. Base64 hat eine schöne Eigenschaft, die Streaming exakt macht: Jedes drei Eingabe-Bytes erzeugen vier unabhängige Ausgabe-Zeichen, also ist der konkatenierte Output identisch mit dem Kodieren der ganzen Datei auf einmal, solange jedes Chunk, das Sie kodieren, ein Vielfaches von drei Bytes ist. Brechen Sie die Ausrichtung, und der Output ändert sich, denn eine Chunk-Grenze spaltet eine Drei-Byte-Gruppe mitten im Stream:

import Foundation

func streamEncode(_ input: InputStream, output: OutputStream, lineLength: Int = 76) throws {
  input.open()
  output.open()
  defer { input.close(); output.close() }
  var buffer = [UInt8](repeating: 0, count: 65_536)
  var pending = [UInt8]()
  var line = ""
  var lineCount = 0
  func addText(_ text: String) {
    line += text
    while line.count > lineLength {
      if lineCount > 0 { _ = output.write(Array("\r\n".utf8), maxLength: 2) }
      _ = output.write(Array(String(line.prefix(lineLength)).utf8), maxLength: lineLength)
      line = String(line.dropFirst(lineLength))
      lineCount += 1
    }
  }
  func flushGroup(_ group: [UInt8]) {
    addText(Data(group).base64EncodedString())
  }
  while input.hasBytesAvailable {
    let n = input.read(&buffer, maxLength: buffer.count)
    if n < 0 { throw CocoaError(.fileReadUnknown) }
    if n == 0 { break }
    pending.append(contentsOf: buffer[0..<n])
    let groups = pending.count / 3
    if groups > 0 {
      flushGroup(Array(pending[0..<(groups * 3)]))
      pending.removeFirst(groups * 3)
    }
  }
  if !pending.isEmpty {
    flushGroup(pending)
  }
  if !line.isEmpty {
    if lineCount > 0 { _ = output.write(Array("\r\n".utf8), maxLength: 2) }
    _ = output.write(Array(line.utf8), maxLength: line.utf8.count)
  }
}

Der Spitzenspeicher ist ein Lese-Puffer plus die aktuelle Zeile, egal wie groß die Datei ist, und der umgebrochene Output entspricht der Einmal-Schreibweise von .lineLength76Characters exakt. Dieselbe Drei-Vielfach-Regel, mit umgekehrten Rollen, ist die, auf der der Streaming-Dekodierer im Schwester-Artikel steht, so dass die beiden Seiten der Reise eine arithmetische Wahrheit teilen.

Große Payloads und die Speicher-Rechnung

Machen wir die Arithmetik, die Sie brauchen, wenn das nächste Mal jemand fragt "können wir das base64-kodieren?". Jedes drei Eingabe-Bytes werden zu vier Ausgabe-Zeichen, also multipliziert sich die Größe mit 4/3: Eine 100-Kilobyte-Datei wird zu einem 133.336-Zeichen-String, eine 10-Megabyte-Datei zu 13.333.336 Zeichen, und so weiter. Padding fügt ganz am Ende höchstens zwei Zeichen hinzu, ein Rundungsfehler bei allem Größerem als ein paar Bytes, und die leere Eingabe ist die einzige Ausnahme, wo das Finanzamt einen einzigen kostenlosen Durchlass gewährt und das Ergebnis der leere String ist. Drei praktische Konsequenzen. Erstens, veranschlagen Sie vorher: Wenn Ihr Payload schon nahe an einem Limit ist (die Komfortzone einer URL mit rund 2.000 Zeichen, der Vertrag eines JSON-Felds, die Breite einer Datenbank-Spalte), dividieren Sie das Limit vor dem Kodieren durch 1,33, nicht danach (und durch 1,37, wenn Umbruch im Spiel ist). Zweitens, während Sie packen, halten Sie die Original-Bytes und den verpackten String gleichzeitig, also ist der Arbeitsbereich etwa 2,33 Mal das Original, und die Streaming-Funktionen oben sind der Ausweg, wenn diese Zahl aufhört, bequem zu sein. Drittens, die Steuer ist in der Praxis eine Einwegstraße: Sie zahlen sie, wenn Sie packen, und Ihre Bytes kommen heim, wenn jemand auspackt, also ist die echte Frage nie "ist base64 teuer?", sondern "fordert der nur-Text-Weg, auf dem ich bin, das"?.

Die Fehler, die beißen

  • Der failable Zeichensatz-Schritt. String.data(using:) kann nil zurückgeben (probieren Sie .ascii mit einem akzentuierten Zeichen), und der Force-unwrap davon ist das klassische Upgrade eines schlechten Inputs in eine abgestürzte App. Wachen Sie über die Umwandlung, nicht nur über den base64-Aufruf, der der einfache Teil ist.
  • Der CRLF-Hausstil. Eine .lineLength-Option ohne Zeilenende-Option erzeugt CRLF als Standard. Wenn Ihr Format nur LF will und Sie die Option vergessen, trägt Ihr Output Wagenrückläufe, die es nie hätte haben sollen.
  • Die nur-CR-Falle. .endLineWithCarriageReturn allein erzeugt Zeilenenden im alten Mac-Stil mit nur CR. Wenn Sie CRLF meinten (und für MIME meinen Sie es), geben Sie beide Zeilenende-Optionen an.
  • Umbruch innerhalb von JSON. Ein roher Zeilenumbruch innerhalb eines JSON-Strings ist ungültiges JSON, Punkt. Umgebrochenes base64, in ein Dokument eingebaut, ist ein Syntaxfehler mit base64-Akzent. Behalten Sie umgebrochenen Output in E-Mail-Bodies und Zertifikatsdateien.
  • Der BOM-Mitfahrer. Die einfache .utf16-Umwandlung hängt eine zweibyte BOM vor, der in Ihr verpacktes Ergebnis reist und Dekodierer verwirrt, die ihn nicht erwartet haben. Verwenden Sie .utf16LittleEndian oder .utf16BigEndian, wenn Sie UTF-16 ohne das Label brauchen.
  • Dialekt-Drift. Standard und base64url sind verschiedene Alphabete, und der RFC sagt es schriftlich. Ein +, das in einen Query-String überlebt, wird zu einem Leerzeichen; ein -, das einen laxen Standard-Dekodierer erreicht, wird gelöscht. Wählen Sie den Dialekt an der Grenze und behalten Sie ihn.
  • Groß-/Kleinschreibung ist ein Buchstabe. Das Alphabet unterscheidet A von a. Ein kopiertes Einfügen mit zusammengelegter Groß-Klein-Schreibung oder ein eifriger Großschreibungs-Aufruf kaputtmacht die Daten still und leise, denn beide Versionen bestehen weiterhin jede Alphabet-Prüfung. Base64 ist groß-/kleinschreibungsempfindlich wie eine Passnummer.
  • Die Ausrichtungsregel. Streaming-Kodierer müssen Chunks an Vielfachen von drei Bytes schneiden. Ein nicht ausgerichteter Chunk ändert den Output, und die Änderung ist still: Der String dekodiert weiterhin, zu den falschen Daten.
  • Die doppelte Umhüllung. Zwei Ebenen, die beide kodieren, produzieren base64 von base64. Der Leser, der einmal dekodiert, sieht Buchstaben, wo Bytes sein sollten, und der Vorfall schreibt sich selbst.
  • Die Verfügbarkeitsmauer. Die neuen nativen Optionen (.base64URLAlphabet, .omitPaddingCharacter) existieren auf den neuesten SDK-Betas und in der Open-Source-Foundation hinter einem Verfügbarkeitsmarker, aber nicht auf jeder Toolchain, die Ihr CI anfasst. Wenn Sie sie übernehmen, wachen Sie mit Verfügbarkeitsprüfungen, damit derselbe Quellcode auf älteren Xcode-Versionen und auf Linux kompiliert. Auf der aktuellen stabilen Toolchain kompiliert die vierzeilige Extension überall, wo die Optionen nicht gehen.
  • Base64 ist keine Verschlüsselung. Wenn die Anforderung Vertraulichkeit ist, haben Sie sich um eine ganze Kategorie im Werkzeug geirrt. base64s Job ist, Bytes reisen zu lassen, und es erledigt genau diesen Job, nicht mehr.

Wie man es ausliefert

  • Kodieren Sie Bytes, keine Wünsche. Entscheiden Sie die Byte-Form, bevor Sie die Methode aufrufen, UTF-8 als Standard und explizit benannt, wenn es das nicht ist, und wachen Sie über den failable data(using:)-Schritt, denn dort gehen Daten tatsächlich abhanden.
  • Standardmäßig ohne Umbruch, umgebrochen nach Vertrag. Die einfache Einzeilen-Ausgabe ist korrekt für JSON, APIs und die meisten Datenbanken; greifen Sie nach den 64/76-Umbruch-Optionen nur, wenn das empfangende Format sie verlangt, und zahlen Sie für beide Zeilenende-Optionen, wenn Sie CRLF meinen.
  • Ein Dialekt pro Grenze. Standard-base64 für textlastige Ziele, base64url für alles, was eine URL oder einen Dateinamen berühren wird, niemals die beiden in demselben Dokument. Schreiben Sie die Umwandlung einmal, nennen Sie sie ehrlich und wiederverwenden Sie sie.
  • Budgetieren Sie den Zuschlag. Multiplizieren Sie vor dem Start mit 4/3 (mit 1,37, wenn Umbruch im Spiel ist), und streamen Sie mit 3-Byte-ausgerichteten Chunks, wenn der Payload groß genug ist, um den Arbeitsbereich unbequem zu machen.
  • Verwenden Sie kein Paketband als Schloss. Wenn die Anforderung Geheimhaltung ist, bleiben Sie am base64-Regal stehen und nehmen Sie Verschlüsselung mit.

Eine kurze Geschichte des Packens

Das Alphabet, mit dem Sie packen, und die Zeilenlängen, an die Sie umbrechen, sind Fossilien aus vier Jahrzehnten von Streitigkeiten darüber, wie viel binär einen nur-Text-Weg überleben kann, und Swifts Stellung in dieser Geschichte ist kurz, aber interessant:

  • 1980er, das Ära der gleichen Maschine. Die ersten Kodierer dieser Familie existierten, um Dateien über Modems zwischen Systemen zu bewegen, die annahmen, das andere Ende sei eine Maschine wie ihre eigene. uuencode auf UNIX verwendete Großbuchstaben, Ziffern und Satzzeichen, und seine Erfinder fanden einen Trick, der Rechenleistung sparte: Das Alphabet liegt an aufeinanderfolgenden ASCII-Positionen, also bestand das Kodieren buchstäblich darin, "32 hinzuzählen", ohne Nachschlagetabelle. BinHex, der Cousin, der 1981 auf dem TRS-80 geboren wurde, hüpfte auf den Apple II, wurde 1984 das Format des klassischen Macintosh und wettete auf etwas anderes: Seine 64 Zeichen lassen 7, O, W, g, o und fast die Hälfte der Kleinbuchstaben aus.
  • 1987, das Alphabet bekommt eine Adresse. RFC 989, die erste Privacy-Enhanced-Mail-Spezifikation, standardisierte genau die 64 Zeichen, die Sie heute tippen, brach die Ausgabe bei 64 Zeichen pro Zeile um und verwendete = für Padding und *, um kodiert-aber-unverschlüsselte Daten zu markieren. Jeder PEM-artige Block, den Sie je in eine Server-Konfiguration gepackt haben, ist ein Nachkomme dieses Dokuments.
  • 1996, das liberale Ära. MIME (RFC 2045) nahm das Alphabet für E-Mail-Anhänge, verlegte den Umbruch auf 76 Zeichen und fügte die Regel hinzu, die Umbruch erzeugbar machte: Dekodierer sollten die Zeilenumbrüche ignorieren. Kodierer lernten, zu umbrechen; Dekodierer lernten, zu verzeihen. Swifts 76-Zeichen-Option ist ein lebendes Andenken an genau diese Debatte.
  • 2003 bis 2006, die Regeln härten aus. RFC 3548 (2003) erklärte, dass Padding nicht übersprungen werden darf (es sei denn, ein Format sagt etwas anderes) und dass Dekodierer Zeichen außerhalb des Alphabets verwerfen müssen; RFC 4648 (Oktober 2006) entschied die Familie und fügte das URL-sichere Alphabet hinzu, ausdrücklich damit lange Kennungen in URLs leben konnten, ohne jedes Sonderzeichen prozent-kodiert. Die Konvention "kein Padding im URL-Dialekt" wurde im selben Dokument geboren, denn ein Pad-Zeichen in einer URL wird typischerweise zu %3D, was den Sinn zunichtemacht.
  • 2013 bis 2014, die API ist bereits da. Apples NSData-Klasse hatte seit Jahren base64 verpackt, und die optionenbasierte API mit den vier Umbruch-Optionen kam in iOS 7, im Jahr 2013, bevor Swift überhaupt existierte. Als Swift 1.0 am 9. September 2014 ankam, erbte es einen totalen Kodierer mit vier Umbruch-Optionen und das 64-Buchstaben-Alphabet von 1987, und die Persönlichkeit hat sich seitdem nicht verändert.
  • 3. Dezember 2015, die Toolchain verlässt das Gebäude. Swift wurde an diesem Tag open-sourced, und Foundations base64 überquerte mit ihm nach Linux und später nach Windows. "Base64-Kodierung in Swift weg von einer Apple-Maschine" ist knappe zehn Jahre alt: ein sehr junger Gast auf einer Party, die 1987 begann.
  • 2023 bis 2026, der Rewrite und der URL-Dialekt. Der Foundation-Rewrite (das swift-foundation-Projekt) bewegte Data in einen reinen Swift-Kern, und 2025 fügte ein Community-Pitch native base64url- und Padding-Weglass-Optionen hinzu. Stand jetzt liefern die neuesten SDK-Betas und die Open-Source-Toolchain die Kodierungs-Optionen, der Rest der Familie wächst in der Open-Source-Foundation hinter Verfügbarkeitsmarkern heran, und die Community-Extension bleibt derweil die portable Brücke.

Kleine Freuden

  • Ein Megabyte packt zu exakt 1.333.336 base64-Zeichen, die 4/3-Steuer plus zwei Padding-Zeichen, genau auf die Ziffer. Die einzige Eingabe, die der Steuer komplett entkommt, ist die leere: nichts rein, nichts raus.
  • Der Kodierer ist total auf eine Art, die der Dekodierer nicht ist. Er gibt nie nil zurück, wirft nie etwas, lehnt nie ab. Der einzige Fehler in der ganzen Pipeline lebt upstream, im Zeichensatz-Schritt, deshalb fühlt sich die Methode so viel ruhiger an als ihre Cousine.
  • Kodieren Sie das Wort héllo in UTF-8, und es wird zu aMOpbGxv; kodieren Sie es in UTF-16 little-endian, und es wird zu aADpAGwAbABvAA==. Dasselbe Wort, zwei verschiedene Pässe, beide gültig, keines austauschbar.
  • Das Testwort der base64-Welt ist foobar, und es packt zu Zm9vYmFy. Wenn Sie je ein base64-Beispiel in der Wildnis gesehen haben, ist es ziemlich wahrscheinlich, dass foobar beteiligt war.
  • Das berühmte 1x1-transparente GIF ist 42 Bytes groß und beginnt mit dem magischen Wort GIF89a, deshalb taucht das Präfix R0lGODlh in mehr Codebasen auf der Erde auf als fast jeder andere base64-String.
  • Ihr Codable-Struct hat wahrscheinlich jahrelang base64 geschickt, ohne dass Sie es merkten: Die Standard-Data-Strategie von JSONEncoder packt mit Standard-base64, deshalb kreuzt ein Data-Feld die Leitung als gepadderter String statt als Zahlen-Array.
  • Padding überschreitet nie zwei Zeichen, nie. Ein Payload von 1 Byte endet in ==, ein Payload von 2 Bytes endet in =, und ein Payload von 3 Bytes endet in nichts. Die gesamte Grammatik der letzten Gruppe passt auf einen Fingernagel.
  • Swift ist 27 Jahre jünger als das Alphabet, mit dem es packt. Die Sprache erschien 2014; die 64 Buchstaben wurden 1987 standardisiert und haben sich seitdem nicht verändert.

Das ist die komplette Pack-Werkzeugkiste: eine totale Methode, ein failable Schritt, der davor kommt, vier Umbruch-Optionen mit CRLF-Hausstil, eine vierzeilige base64url-Extension, eine 3-Byte-Ausrichtungsregel für Streaming und ein 4/3-Zuschlag, der der Eintrittspreis auf den nur-Text-Weg ist. Kodieren ist der Ort, an dem Sie die Rechnung von base64 bezahlen, und Sie kennen jetzt jeden Posten, bevor Sie unterschreiben. Im Moment, in dem Sie die Reise umdrehen und anfangen, zu öffnen, was andere gepackt haben, übernehmen die nils, die Leerzeichen-Urteile und der blinde Fleck des laxen Knopfs die Bühne. Der verwandte Dekodierungs-Artikel spielt die komplette Show auf dieser Hälfte der Rundreise, also wenn die Buchstaben anfangen, anzukommen, wissen Sie bereits genau, wie Sie sie öffnen.

Zuletzt aktualisiert: 2026-09-08

Verwandter Artikel: Base64-Dekodierung in Swift: Ein vollständiger Leitfaden