Enviar um arquivo
  1. base64hub
  2. Base64 no código
  3. Swift
Código

Base64 em Swift

O Swift usa o Data do Foundation para Base64: um método para codificar e um inicializador falível para decodificar. Os trechos abaixo foram executados com Swift 6.1 no Linux, que usa a mesma API do Foundation das plataformas Apple.

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:

EntradaProblema
PDw_Pz8-Pg==caracteres URL-safe
aGkfalta de preenchimento
aGk=!!lixo depois do preenchimento
aGk==preenchimento demais
acomprimento 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 um Data mostra algo como 6 bytes, não o conteúdo. Use base64EncodedString() ou String(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.

Continue lendo