Codificar e decodificar uma string
O String.utf8 dá os bytes UTF-8; o Data os 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
}
Há dois optionals no caminho da decodificação. Data(base64Encoded:) é nil para Base64 inválido, e String(data:encoding:) é nil quando os bytes não são UTF-8 válido (por exemplo [0xFF, 0xFE]). Se preferir caracteres de substituição a nil, use String(decoding: data, as: UTF8.self).
Base64 URL-safe
O Foundation não conhece o alfabeto URL-safe, então converta os caracteres você mesmo:
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)) // <<???>>
A etapa do preenchimento é obrigatória: Data(base64Encoded: "aGk") retorna nil. JWTs, identity tokens do Sign in with Apple e muitos payloads de notificações push usam essa forma sem preenchimento. Veja Base64 vs Base64URL e preenchimento explicado para os detalhes, ou inspecione um token no decodificador de JWT.
Validação: quando a decodificação retorna nil
Toda entrada inválida dá o mesmo resultado, nil, sem mensagem de erro. Todas estas retornaram nil:
| Entrada | Problema |
|---|---|
PDw_Pz8-Pg== | caracteres URL-safe |
aGk | falta de preenchimento |
aGk=!! | lixo depois do preenchimento |
aGk== | preenchimento demais |
a | comprimento impossível |
"aGVs\nbG8K" | quebra de linha |
" aGk=" | espaço no início |
Um caso passa: aGl= (bits de preenchimento diferentes de zero) decodifica para hi. Uma verificação de validade, portanto, é só Data(base64Encoded: s) != nil. Quando você precisa saber por que uma string falha, o validador de Base64 informa a posição e o motivo, e o guia de erros explica cada um.
Para entrada com quebras de linha ou caracteres perdidos, a opção .ignoreUnknownCharacters pula tudo o que está fora do alfabeto:
Data(base64Encoded: "aGVs\nbG8K", options: .ignoreUnknownCharacters)
// 6 bytes: "hello\n"
Use-a com corpos MIME ou PEM em que você confia. Em entrada arbitrária, ela esconde erros, porque os caracteres URL-safe também são pulados e o resultado fica errado em silêncio.
Opções de quebra de linha
O base64EncodedString(options:) pode quebrar as linhas em 64 ou 76 caracteres. O final de linha padrão é 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 finais de linha Unix, combine a opção de comprimento só com .endLineWithLineFeed: [.lineLength76Characters, .endLineWithLineFeed]. Sem nenhuma opção, a saída fica numa linha só, que é o que você quer para JSON, URLs e headers. Para e-mail, o codificador Base64 MIME mostra o layout de 76 caracteres.
Codificar e decodificar um arquivo
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"))
O force unwrap tudo bem num playground; num app, faça guard let no Data decodificado e informe um erro.
Arquivos grandes em blocos
O Foundation não tem codificador Base64 em streaming. Leia o arquivo em blocos com tamanho múltiplo de 3 para que nenhum preenchimento caia no meio:
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 assim um arquivo de teste de 300 KB gerou a mesma string que codificá-lo numa única chamada. O read(upToCount:) e o write(contentsOf:) exigem macOS 10.15.4 / iOS 13.4 ou posterior. O guia de aumento de tamanho explica por que a saída fica em cerca de 4/3 da entrada.
Armadilhas
- Esperar uma mensagem de erro. O
Data(base64Encoded:)só diz “não”. Para depurar, registre no log o comprimento da entrada e os primeiros caracteres. - Prefixos de data URI. Strings vindas de web views muitas vezes começam com
data:image/png;base64,. Corte na primeira vírgula antes de decodificar, senão o resultado énil. String(describing: data). Imprimir umDatamostra algo como6 bytes, não o conteúdo. Usebase64EncodedString()ouString(decoding:as:).- Tratar Base64 como armazenamento seguro. Use o Keychain para segredos; qualquer pessoa consegue ler Base64. Veja codificação vs criptografia.
Perguntas frequentes
Por que o Data(base64Encoded:) retorna nil?
O inicializador retorna nil para qualquer entrada inválida em vez de lançar um erro: falta de preenchimento, - ou _ do URL-safe, espaços, quebras de linha ou um prefixo data:. Corrija a entrada antes ou passe options: .ignoreUnknownCharacters para entrada com quebras de linha.
O Swift suporta Base64URL nativamente?
O Foundation não tem opção URL-safe. Troque + por - e / por _ depois de codificar e remova os =; faça o inverso e restaure o preenchimento antes de decodificar.
Como converter uma UIImage para Base64 em Swift?
Obtenha os bytes primeiro, por exemplo com image.pngData() ou image.jpegData(compressionQuality: 0.8), e chame .base64EncodedString() no resultado. Para conferir a saída, cole-a em Base64 para imagem.