Закодировать и декодировать строку
String.utf8 даёт байты UTF-8, а Data их кодирует:
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
}
На пути декодирования два опционала. Data(base64Encoded:) равен nil для некорректного Base64, а String(data:encoding:) – nil, если в байтах некорректный UTF-8 (например, [0xFF, 0xFE]). Если вместо nil вы предпочитаете символы замены, используйте String(decoding: data, as: UTF8.self).
URL-safe Base64
Foundation не знает URL-safe алфавита, так что заменяйте символы сами:
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)) // <<???>>
Шаг с дополнением обязателен: Data(base64Encoded: "aGk") возвращает nil. Такую форму без дополнения используют JWT, identity-токены Sign in with Apple и многие payload push-уведомлений. Подробности в статьях Base64 и Base64URL и про дополнение, а токен можно разобрать в декодере JWT.
Проверка: когда декодирование возвращает nil
Любые некорректные данные дают один и тот же результат, nil, без сообщения об ошибке. Все эти строки вернули nil:
| Вход | Проблема |
|---|---|
PDw_Pz8-Pg== | URL-safe символы |
aGk | нет дополнения |
aGk=!! | мусор после дополнения |
aGk== | слишком много дополнения |
a | невозможная длина |
"aGVs\nbG8K" | перевод строки |
" aGk=" | пробел в начале |
Проходит один случай: aGl= (ненулевые биты дополнения) декодируется в hi. Поэтому проверка корректности – это просто Data(base64Encoded: s) != nil. Когда нужно понять, почему строка не проходит, валидатор Base64 сообщит позицию и причину, а статья об ошибках объяснит каждую.
Для данных с переносами строк или посторонними символами есть параметр .ignoreUnknownCharacters, который пропускает всё вне алфавита:
Data(base64Encoded: "aGVs\nbG8K", options: .ignoreUnknownCharacters)
// 6 bytes: "hello\n"
Используйте его для тел MIME или PEM, которым доверяете. На произвольных данных он прячет ошибки: URL-safe символы тоже пропускаются, и результат молча оказывается неверным.
Параметры переноса строк
base64EncodedString(options:) умеет переносить строки через 64 или 76 символов. По умолчанию строки заканчиваются на 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)
Для окончаний строк в стиле Unix добавьте к параметру длины только .endLineWithLineFeed: [.lineLength76Characters, .endLineWithLineFeed]. Без параметров вывод получается одной строкой, и такой подходит для JSON, URL и заголовков. Для почты разметку по 76 символов показывает кодировщик MIME Base64.
Закодировать и декодировать файл
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"))
Принудительное извлечение опционала допустимо в playground; в приложении проверьте декодированный Data через guard let и сообщите об ошибке.
Большие файлы по частям
Потокового кодировщика Base64 в Foundation нет. Читайте файл частями, размер которых кратен 3, чтобы дополнение не попало в середину:
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))
}
}
На тестовом файле в 300 КБ результат совпал с кодированием всего файла сразу. read(upToCount:) и write(contentsOf:) требуют macOS 10.15.4 / iOS 13.4 или новее. Почему вывод получается примерно в 4/3 от входа, объясняет статья о росте размера.
Подводные камни
- Ожидание сообщения об ошибке.
Data(base64Encoded:)говорит только «нет». Для отладки логируйте длину входной строки и её первые символы. - Префиксы data URI. Строки из веб-представлений часто начинаются с
data:image/png;base64,. Перед декодированием отрежьте всё до первой запятой, иначе получитеnil. String(describing: data). При печатиDataвы увидите что-то вроде6 bytes, а не содержимое. Используйтеbase64EncodedString()илиString(decoding:as:).- Base64 как безопасное хранилище. Секреты храните в Keychain; Base64 может прочитать кто угодно. См. кодирование и шифрование.
Вопросы и ответы
Почему Data(base64Encoded:) возвращает nil?
На любых некорректных данных инициализатор не бросает ошибку, а возвращает nil: при отсутствии дополнения, URL-safe символах - или _, пробелах, переводах строк или префиксе data:. Сначала исправьте строку, а для данных с переносами передайте options: .ignoreUnknownCharacters.
Поддерживает ли Swift Base64URL из коробки?
В Foundation нет URL-safe режима. После кодирования замените + на -, а / на _ и уберите =; перед декодированием сделайте обратную замену и восстановите дополнение.
Как перевести UIImage в Base64 на Swift?
Сначала получите байты, например image.pngData() или image.jpegData(compressionQuality: 0.8), затем вызовите у результата .base64EncodedString(). Чтобы проверить вывод, вставьте его в Base64 в картинку.