Subir un archivo
  1. base64hub
  2. Base64 en código
  3. Swift
Código

Base64 en Swift

Swift usa el Data de Foundation para Base64: un método para codificar y un inicializador que puede fallar para decodificar. Los fragmentos de esta página se ejecutaron con Swift 6.1 en Linux, que usa la misma API de Foundation que las plataformas de Apple.

Codificar y decodificar una cadena

String.utf8 te da los bytes UTF-8; Data los codifica:

import Foundation

let enc = Data("héllo wörld".utf8).base64EncodedString()
print(enc)   // aMOpbGxvIHfDtnJsZA==

if let data = Data(base64Encoded: enc),
   let s = String(data: data, encoding: .utf8) {
    print(s) // héllo wörld
}

En el camino de decodificación hay dos opcionales. Data(base64Encoded:) es nil si el Base64 no es válido, y String(data:encoding:) es nil cuando los bytes no son UTF-8 válido (por ejemplo, [0xFF, 0xFE]). Si prefieres caracteres de reemplazo en lugar de nil, usa String(decoding: data, as: UTF8.self).

Base64 URL-safe

Foundation no conoce el alfabeto URL-safe, así que convierte tú los caracteres:

let d = Data("<<???>>".utf8)
let b64url = d.base64EncodedString()
    .replacingOccurrences(of: "+", with: "-")
    .replacingOccurrences(of: "/", with: "_")
    .replacingOccurrences(of: "=", with: "")
print(b64url)   // PDw_Pz8-Pg

func base64URLDecode(_ s: String) -> Data? {
    var b = s.replacingOccurrences(of: "-", with: "+")
             .replacingOccurrences(of: "_", with: "/")
    b += String(repeating: "=", count: (4 - b.count % 4) % 4)
    return Data(base64Encoded: b)
}

print(String(decoding: base64URLDecode("PDw_Pz8-Pg")!, as: UTF8.self))  // <<???>>

El paso del relleno es obligatorio: Data(base64Encoded: "aGk") devuelve nil. Los JWT, los identity tokens de Sign in with Apple y muchos payloads de notificaciones push usan esta forma sin relleno. Consulta Base64 frente a Base64URL y el relleno explicado para más detalles, o inspecciona un token en el decodificador JWT.

Validación: cuando decodificar devuelve nil

Todas las entradas no válidas dan el mismo resultado, nil, sin ningún mensaje de error. Todas estas devolvieron nil:

EntradaProblema
PDw_Pz8-Pg==caracteres URL-safe
aGkfalta el relleno
aGk=!!basura después del relleno
aGk==demasiado relleno
alongitud imposible
"aGVs\nbG8K"salto de línea
" aGk="espacio inicial

Un caso pasa: aGl= (bits de relleno distintos de cero) se decodifica como hi. Por tanto, una comprobación de validez se reduce a Data(base64Encoded: s) != nil. Cuando necesites saber por qué falla una cadena, el validador Base64 indica la posición y el motivo, y la guía de errores explica cada uno.

Para entradas con saltos de línea o caracteres sueltos, la opción .ignoreUnknownCharacters se salta todo lo que esté fuera del alfabeto:

Data(base64Encoded: "aGVs\nbG8K", options: .ignoreUnknownCharacters)
// 6 bytes: "hello\n"

Úsala con cuerpos MIME o PEM en los que confíes. Con entradas arbitrarias oculta los errores, porque también se salta los caracteres URL-safe y el resultado sale mal sin avisar.

Opciones de corte de línea

base64EncodedString(options:) puede cortar las líneas a 64 o 76 caracteres. El final de línea por defecto es CRLF:

let x = Data(String(repeating: "x", count: 60).utf8)

x.base64EncodedString(options: .lineLength76Characters)
// "eHh4eHh4...eHh4\r\neHh4"    (76 chars, CRLF, rest)

x.base64EncodedString(options: .lineLength64Characters)
// 64 chars, "\r\n", 16 chars   (PEM-style)

Para finales de línea de Unix, combina la opción de longitud solo con .endLineWithLineFeed: [.lineLength76Characters, .endLineWithLineFeed]. Sin ninguna opción, la salida es una sola línea, que es lo que quieres para JSON, URL y cabeceras. Para el correo, el codificador Base64 MIME muestra la disposición de 76 caracteres.

Codificar y decodificar un archivo

let url = URL(fileURLWithPath: "hello.txt")
let b64 = try Data(contentsOf: url).base64EncodedString()
print(b64)   // aGVsbG8K

try Data(base64Encoded: "aGVsbG8K")!.write(to: URL(fileURLWithPath: "copy.txt"))

El desempaquetado forzado vale en un playground; en una app, usa guard let con el Data decodificado e informa del error.

Archivos grandes por bloques

Foundation no tiene un codificador Base64 en flujo. Lee el archivo en bloques cuyo tamaño sea múltiplo de 3 para que no aparezca relleno en mitad de la salida:

func encodeFile(from src: URL, to dst: URL) throws {
    _ = FileManager.default.createFile(atPath: dst.path, contents: nil)
    let input = try FileHandle(forReadingFrom: src)
    let output = try FileHandle(forWritingTo: dst)
    defer { try? input.close(); try? output.close() }

    while let chunk = try input.read(upToCount: 3 * 64 * 1024), !chunk.isEmpty {
        try output.write(contentsOf: Data(chunk.base64EncodedString().utf8))
    }
}

Codificar así un archivo de prueba de 300 KB dio la misma cadena que codificarlo en una sola llamada. read(upToCount:) y write(contentsOf:) necesitan macOS 10.15.4 / iOS 13.4 o posterior. La guía del aumento de tamaño explica por qué la salida ocupa unos 4/3 de la entrada.

Trampas

  • Esperar un mensaje de error. Data(base64Encoded:) solo dice «no». Para depurar, registra la longitud de la entrada y sus primeros caracteres.
  • Prefijos de data URI. Las cadenas que llegan de web views suelen empezar por data:image/png;base64,. Corta por la primera coma antes de decodificar o el resultado será nil.
  • String(describing: data). Al imprimir Data verás algo como 6 bytes, no su contenido. Usa base64EncodedString() o String(decoding:as:).
  • Tratar Base64 como almacenamiento seguro. Usa el Llavero (Keychain) para los secretos; Base64 lo puede leer cualquiera. Consulta codificación frente a cifrado.

Preguntas frecuentes

¿Por qué Data(base64Encoded:) devuelve nil?

El inicializador devuelve nil ante cualquier entrada no válida en lugar de lanzar un error: falta de relleno, - o _ de URL-safe, espacios, saltos de línea o un prefijo data:. Corrige antes la entrada, o pasa options: .ignoreUnknownCharacters si la entrada viene cortada en líneas.

¿Swift admite Base64URL de forma nativa?

Foundation no tiene ninguna opción URL-safe. Después de codificar, sustituye + por - y / por _ y quita los =; antes de decodificar, haz lo contrario y restaura el relleno.

¿Cómo convierto una UIImage a Base64 en Swift?

Obtén primero los bytes, por ejemplo con image.pngData() o image.jpegData(compressionQuality: 0.8), y después llama a .base64EncodedString() sobre el resultado. Para comprobar la salida, pégala en Base64 a imagen.

Sigue leyendo