문자열 인코딩과 디코딩
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:)는 잘못된 Base64에 대해 nil이고, String(data:encoding:)은 바이트가 올바른 UTF-8이 아닐 때(예: [0xFF, 0xFE]) nil입니다. 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, Sign in with Apple의 ID 토큰, 많은 푸시 알림 페이로드가 이 패딩 없는 형식을 씁니다. 자세한 내용은 Base64와 Base64URL 비교와 패딩 설명을 참고하거나, JWT 디코더로 토큰을 살펴보세요.
검증: 디코딩이 nil을 반환할 때
잘못된 입력은 모두 오류 메시지 없이 같은 결과, 즉 nil을 냅니다. 다음은 모두 nil을 반환했습니다.
| 입력 | 문제 |
|---|---|
PDw_Pz8-Pg== | URL-safe 문자 |
aGk | 패딩 누락 |
aGk=!! | 패딩 뒤의 쓰레기 문자 |
aGk== | 패딩이 너무 많음 |
a | 불가능한 길이 |
"aGVs\nbG8K" | 줄 바꿈 |
" aGk=" | 앞쪽 공백 |
통과하는 경우가 하나 있습니다. aGl=(0이 아닌 패딩 비트)는 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"))
플레이그라운드에서는 강제 언래핑도 괜찮지만, 앱에서는 디코딩한 Data를 guard let으로 받고 오류를 보고하세요.
큰 파일을 조각으로 처리하기
Foundation에는 스트리밍 Base64 인코더가 없습니다. 중간에 패딩이 생기지 않도록 크기가 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 KB 테스트 파일을 인코딩하면 한 번의 호출로 인코딩한 것과 같은 문자열이 나왔습니다. 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를 안전한 저장소로 취급하기. 비밀 정보에는 키체인을 쓰세요. Base64는 누구나 읽을 수 있습니다. 인코딩과 암호화의 차이를 참고하세요.
자주 묻는 질문
Data(base64Encoded:)가 nil을 반환하는 이유는 무엇인가요?
이 이니셜라이저는 잘못된 입력이면 예외를 던지는 대신 nil을 반환합니다. 패딩 누락, URL-safe 문자 -나 _, 공백, 줄 바꿈, data: 접두사가 원인입니다. 먼저 입력을 고치거나, 줄 바꿈된 입력이라면 options: .ignoreUnknownCharacters를 넘기세요.
Swift는 Base64URL을 기본으로 지원하나요?
Foundation에는 URL-safe 옵션이 없습니다. 인코딩한 뒤 +를 -로, /를 _로 바꾸고 =를 제거하세요. 디코딩 전에는 이를 반대로 하고 패딩을 복원하세요.
Swift에서 UIImage를 Base64로 변환하려면 어떻게 하나요?
먼저 image.pngData()나 image.jpegData(compressionQuality: 0.8) 등으로 바이트를 얻은 다음, 그 결과에 .base64EncodedString()을 호출하세요. 출력을 확인하려면 Base64 이미지 변환에 붙여넣으세요.