직렬화와 역직렬화는 객체를 전송 또는 저장을 위한 형식으로 변환하는 기본적인 프로세스입니다. 모바일 개발에서 이러한 메커니즘은 모든 네트워크 요청, 상태 저장 및 프로세스 간 통신에서 사용됩니다. MDN Web Docs, 2024에 따르면, JSON은 웹 및 모바일 애플리케이션에서 가장 인기 있는 직렬화 형식으로 XML과 Protocol Buffers를 능가하고 있습니다.
주요 내용
직렬화는 애플리케이션 객체를 네트워크 전송 또는 디스크 저장에 적합한 형식으로 변환하는 프로세스입니다. 역직렬화는 수신된 데이터에서 객체를 복원하는 역변환을 수행합니다. 모바일 개발에서 직렬화는 API 요청, 화면 상태 저장, 캐싱 및 Intent 또는 Bundle을 통한 데이터 전송에 사용됩니다.
애플리케이션 구성 요소 간 또는 애플리케이션과 서버 간의 모든 상호 작용에는 직렬화가 필요합니다. REST API는 JSON 또는 XML로 데이터를 전송하고, gRPC는 Protocol Buffers를 사용하며, Android의 프로세스 내 통신은 Parcelable을 사용합니다. 직렬화 없이는 프로세스 경계를 넘어 복잡한 객체를 전달하거나 데이터베이스에 저장하는 것이 불가능합니다.
// 직렬화의 일반 원칙
data class User(
val id: Int,
val name: String,
val email: String
)
// 직렬화: 객체 -> JSON
fun serializeUser(user: User): String {
return """{"id":${user.id},"name":"${user.name}","email":"${user.email}"}"""
}
// 역직렬화: JSON -> 객체
fun deserializeUser(json: String): User? {
// JSON을 객체로 파싱
return Gson().fromJson(json, User::class.java)
}
| 형식 | 크기 | 속도 | 가독성 | 타이핑 |
|---|---|---|---|---|
| JSON | 중간 | 높음 | 높음 | 동적 |
| XML | 큼 | 중간 | 높음 | XSD 스키마 |
| Protocol Buffers | 작음 | 매우 높음 | 낮음 | 엄격한 .proto |
| FlatBuffers | 작음 | 최대 | 낮음 | 엄격한 .fbs |
JSON(JavaScript Object Notation)은 JavaScript 객체 구문을 기반으로 하는 경량 텍스트 형식입니다. JSON은 문자열, 숫자, 부울 값, 배열 및 중첩 객체를 지원하여 모바일 애플리케이션의 대부분의 데이터 전송 시나리오를 포괄합니다. 이 형식은 플랫폼에 독립적입니다. 각 생태계는 구문 분석을 위한 내장 도구를 제공합니다.
JSON은 단순성과 다용성으로 인해 우수합니다. 개발자는 기본 사용을 위해 스키마가 필요하지 않습니다. 구조는 구문 분석 중에 동적으로 결정됩니다. 형식의 가독성은 디버깅과 테스트를 단순화합니다. 서버 응답은 모든 개발자 도구에서 볼 수 있습니다. 두 모바일 플랫폼 모두에서 JSON은 타사 라이브러리 없이 기본적으로 처리됩니다.
// Codable — Swift의 네이티브 JSON 직렬화
struct User: Codable {
let id: Int
let name: String
let email: String
}
let jsonString = """{"id":1,"name":"John","email":"john@test.com"}"""
let jsonData = Data(jsonString.utf8)
let decoder = JSONDecoder()
let user = try! decoder.decode(User.self, from: jsonData)
Protocol Buffers(protobuf)는 고성능 시스템을 위해 Google이 개발한 바이너리 직렬화 형식입니다. JSON과 달리 protobuf는 .proto 파일에 미리 정의된 스키마가 필요하지만 훨씬 작은 데이터 크기와 더 높은 처리 속도를 제공합니다. 이 형식은 gRPC, Firebase Firestore 및 Google 내부 서비스에서 사용됩니다.
protobuf 스키마는 각각 고유 번호가 있는 유형화된 필드를 가진 메시지를 설명합니다. protoc 컴파일러는 직렬화와 역직렬화를 자동으로 수행하는 대상 언어의 클래스를 생성합니다. Protobuf는 하위 호환성을 잃지 않고 필드를 추가 및 제거하는 규칙을 통해 스키마 발전을 지원합니다.
// user.proto의 스키마 정의
syntax = "proto3";
message User {
int32 id = 1;
string name = 2;
string email = 3;
repeated string roles = 4;
}
// Kotlin으로 생성된 코드
val user = UserProto.User.newBuilder()
.setId(1)
.setName("John")
.setEmail("john@test.com")
.build()
val bytes: ByteArray = user.toByteArray()
XML(eXtensible Markup Language)은 엄격한 구조, 네임스페이스 지원 및 XSD 스키마를 통한 검증을 제공하는 형식입니다. XML은 컴팩트함과 구문 분석 속도에서 JSON에 뒤지지만 Android 개발에서 레이아웃 파일, AndroidManifest, 리소스 및 Gradle 구성에 대한 수요가 있습니다. iOS에서는 XML이 plist 파일 및 일부 레거시 서비스에서 사용됩니다.
Android는 XML 구문 분석에 세 가지 접근 방식을 제공합니다: DOM(전체 문서를 메모리에 로드), SAX(이벤트 기반 스트리밍 구문 분석) 및 XmlPullParser(하이브리드 접근 방식)입니다. XmlPullParser는 모바일 장치에 권장되는 옵션으로, 제한된 메모리에서 효율적으로 작동하며 읽으면서 문서를 처리할 수 있습니다.
// Android의 XmlPullParser
fun parseUserXml(inputStream: InputStream): User? {
val parser = Xml.newPullParser()
parser.setInput(inputStream, Xml.Encoding.UTF_8.name)
var id: Int? = null
var name: String? = null
var email: String? = null
while (parser.next() != XmlPullParser.END_DOCUMENT) {
if (parser.eventType == XmlPullParser.START_TAG
&& parser.name == "email") {
email = parser.nextText()
}
}
return User(id!!, name!!, email!!)
}
각 플랫폼은 자체 직렬화 도구를 제공합니다. iOS에서 기본 메커니즘은 JSONEncoder 및 JSONDecoder를 사용하는 Codable 프로토콜이고, Objective-C는 NSJSONSerialization을 사용합니다. Android는 Gson, Moshi 및 kotlinx.serialization 라이브러리를 사용합니다. 프로세스 간 데이터 전송을 위해 Android는 Parcelable을 사용하고 iOS는 NSKeyedArchiver를 사용합니다. Kotlin 프로젝트는 점점 더 kotlinx.serialization을 선택하고 있습니다. 이는 JetBrains의 솔루션으로 멀티플랫폼을 지원하고 리플렉션에 의존하지 않으며 Kotlin 컴파일러 플러그인을 통해 컴파일 타임에 직렬화기를 생성합니다.
Android는 Intent 및 Bundle에 대해 두 가지 직렬화 메커니즘을 지원합니다. Serializable은 리플렉션을 사용하는 표준 Java 메커니즘으로 성능이 낮습니다. Parcelable은 Android 특화 프로토콜로 writeToParcel 및 createFromParcel 메서드의 수동 구현이 필요하지만 직접 바이트 조작으로 인해 수십 배 더 빠르게 작동합니다.
// Android의 Parcelable
@Parcelize
data class UserParcel(
val id: Int,
val name: String,
val email: String
) : Parcelable
// Intent를 통한 전송
val intent = Intent(this, DetailActivity::class.java)
intent.putExtra("user", userParcel)
startActivity(intent)
iOS에서 컨트롤러 간 데이터 전송에는 Data로의 사전 직렬화를 통한 Codable이 사용되고, 프로세스 간 통신에는 NSKeyedArchiver가 사용되어 객체를 바이너리 Property List 형식으로 변환합니다. 최신 SwiftUI 프로젝트는 애플리케이션 모듈 간 전송을 위해 JSONEncoder를 통해 데이터를 인코딩하는 것을 선호합니다.
신뢰할 수 없는 데이터의 잘못된 역직렬화는 취약점으로 이어질 수 있습니다. 직렬화 공격은 Java에서 재정의된 readObject 메서드나 타사 라이브러리의 안전하지 않은 역직렬화기를 악용합니다. Android에서는 신뢰할 수 없는 소스의 데이터에 Serializable을 피하고 대신 Parcelable 또는 역직렬화 후 모든 필드의 수동 검증을 사용해야 합니다. iOS에서 JSONDecoder는 기본적으로 타입에 대해 엄격하지만 JSONSerialization으로 작업할 때 개발자는 조건부 캐스팅을 통해 타입을 확인해야 합니다.
직렬화 형식을 선택할 때 성능 요구 사항, 데이터 크기 및 호환성이 고려됩니다. REST API 및 마이크로서비스 아키텍처의 경우 JSON이 최적의 선택으로 남아 있으며 모든 플랫폼과 언어에서 지원됩니다. 고부하 시스템 및 제한된 대역폭의 모바일 애플리케이션의 경우 더 작은 크기와 더 빠른 역직렬화를 제공하는 Protocol Buffers가 선호됩니다. XML은 구성 시나리오 및 레거시 시스템과의 통합에서만 정당화됩니다. 최신 프로젝트에서는 역직렬화 단계가 없는 바이너리 형식인 FlatBuffers의 인기도 높아지고 있으며 게임 엔진 및 지연 시간에 민감한 애플리케이션에서 사용됩니다. 각 형식에는 자체 영역이 있으며 올바른 선택은 애플리케이션 속도와 소비 트래픽 양에 직접적인 영향을 미칩니다.
자주 묻는 질문
직렬화는 애플리케이션 객체를 네트워크를 통해 보내거나 파일에 저장할 수 있는 형식으로 패키징하는 것입니다. 물건의 사진을 찍는다고 상상해 보세요. 사진은 직렬화이고, 사진에서 물건을 복원하는 것은 역직렬화입니다.
JSON은 사람이 읽을 수 있고 스키마가 필요하지 않은 텍스트 형식입니다. Protocol Buffers는 필수 스키마(.proto)가 있는 바이너리 형식으로 크기가 훨씬 작고 속도가 더 빠릅니다. Protobuf는 고부하 시스템에 선택되고 JSON은 범용 호환성을 위해 선택됩니다.
XML은 구성 파일(AndroidManifest, 레이아웃 리소스), 복잡한 중첩이 있는 문서 및 XSD를 통한 엄격한 검증이 필요한 시스템의 표준으로 남아 있습니다. XML은 네임스페이스 지원이 필요한 SOAP 프로토콜 및 레거시 시스템에서도 사용됩니다.
Codable은 자동 직렬화 및 역직렬화를 위해 Encodable과 Decodable을 결합한 Swift 프로토콜입니다. 컴파일러는 구조체 또는 클래스의 모든 속성에 대해 encode(to:) 및 init(from:) 메서드의 구현을 생성합니다.
Parcelable은 리플렉션을 사용하지 않고 바이트 스트림에 직접 필드를 쓰기 때문에 Serializable보다 훨씬 빠르게 작동합니다. 최신 장치에서는 Parcelable 쪽이 최대 10배까지 빠를 수 있습니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.