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:
| Entrada | Problema |
|---|---|
PDw_Pz8-Pg== | caracteres URL-safe |
aGk | falta el relleno |
aGk=!! | basura después del relleno |
aGk== | demasiado relleno |
a | longitud 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 imprimirDataverás algo como6 bytes, no su contenido. Usabase64EncodedString()oString(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.