Загрузить файл
  1. base64hub
  2. Base64 в коде
  3. Swift
Код

Base64 в Swift

Для Base64 в Swift используется Data из Foundation: один метод для кодирования и один failable-инициализатор для декодирования. Фрагменты ниже запущены на Swift 6.1 в Linux, где Foundation даёт тот же API, что и на платформах Apple.

Закодировать и декодировать строку

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 в картинку.

Читайте также