👋 들어가기 전
또 다시 오랜만에 포스팅을 하게 됐다.
지금은 익명의 프로젝트에 급작스럽게 합류하여 앱 개발을 하던 중 신선한 경험을 하게되어
그 경험을 적어보려한다.
이번 시간의 주제는 동적 키 형태를 디코딩 하는 방법을 간단하게 정리해보자.
✊ 문제발생
만약 서버에서 다음과 같은 형태로 데이터를 보낸다면 우리는 어떻게 처리해야할까 ??
// Case1
{
"data": {
"a":100
}
}
// Case2
{
"data": {
"b": nil
}
}
// Case3
{
"data": {
"c": -100
}
}
어디가 불편할까 ?? data안의 key값이 계속 변하는 상황이다..
☝️서버 개발자님 "해줘"
서버 개발자님 key값 통일해주세요 ~~
단 칼에 거절 백엔드 상황을 들어보니 충분히 납득할만한 이유가 있었다!
그러면 내가 해결을 해야한다는건데 애플도 이런 경우가 있겠지!
✌️ Decodable
우리는 서버로부터 데이터를 받은 후 디코딩을 진행하는데 그 때 DTO들은 맹목적으로
"Decodable" 프로토콜을 채택하는데 오늘 이 프로토콜의 진정한 힘을 써본 날인 것 같다.
public protocol Decodable {
init(from decoder: any Decoder) throws
}
Decodable은 위와 같은 생성자가 있는데 이 기능을 사용하면 dynamic key 형태가 무섭지 않다.
물론 많을수록 작업량이 늘어나긴하지만 불가능하지는 않다.
필요한 과정을 스탭별로 알아보자.
1. CodingKeys
먼저 코딩키를 설정해 각 원소들을 가져올 준비를 한다.
struct SomeDTO: Decodable {
var data: Int?
var otherData: Int?
enum CodingKeys: String, CodingKey {
case data = "data"
}
}
위 코딩키는 너무 간단하지만 실제 내가 받은 형태는 복잡하기때문에 코딩키가 필요했다.
2. Decodable init
struct SomeDTO: Decodable {
var data: Int?
enum CodingKeys: String, CodingKey {
case data = "data"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self) // 정의한 키로 내용 가져오기
let dict = try container.decode([String: Int]?.self, forKey: .data) // 특정키를 해당 타입으로 디코딩
self.data = dict?.values.first // 값 가져오기
}
}
형태가 조금 이상하지만 우리가 지금 서버로부터 받는 형태는 data키에 [String: Int?] 형태로 데이터가
딱 1개만 온다.
만약 조금 더 복잡한 형태는 다음과 같이 진행할 수 있다.
😭 complex Form
아래 주식 DTO를 가정한다.
{
"AAPL":{"quote":{},"chart":[]},
"MSFT":{"quote":{},"chart":[]}
}
단일 DTO 정의
key는 디코딩을 위한 변수이므로 추후 사용은 하지 앟는다.
struct StockModel : Codable {
let quote: String
let latestPrice: Double
let key: String
private enum CodingKeys: CodingKey {
case quote
case latestPrice
}
// Init
init(quote: String, latestPrice: Double, key: String) {
self.quote = quote
self.latestPrice = latestPrice
self.key = key
}
// Decode
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
guard let key = container.codingPath.first?.stringValue else {
throw NSError(domain: "Key not found", code: 0, userInfo: nil)
}
self.quote = try container.decode(String.self, forKey: .quote)
self.latestPrice = try container.decode(Double.self, forKey: .latestPrice)
self.key = key
}
Root DTO 정의
struct StockModels : Codable {
let models: [StockModel]
private struct StockKey : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) { return nil }
}
// Init
init(_ models: [StockModel]) {
self.models = models
}
// Decode
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StockKey.self)
var models: [StockModel] = []
for key in container.allKeys {
if let key = StockKey(stringValue: key.stringValue) {
let model = try container.decode(StockModel.self, forKey: key)
models.append(model)
}
}
self.models = models
}
}
👍 Nested Decode
Decodable 프로토콜을 이용하면 복잡한 중첩형태도 쉽게 풀어낼 수 있다.
{
"user": {
"id": 123,
"details": {
"name": "John",
"age": 30
}
}
}
struct User: Decodable {
let id: Int
let name: String
let age: Int
enum CodingKeys: String, CodingKey {
case user
case id
case details
case name
case age
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// 현재 "user" 키로 이동
let userContainer = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .user)
id = try userContainer.decode(Int.self, forKey: .id)
// "user.details" 키로 이동
let detailsContainer = try userContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .details)
name = try detailsContainer.decode(String.self, forKey: .name)
age = try detailsContainer.decode(Int.self, forKey: .age)
// 디코딩 경로 추적
print("Current coding path: \(detailsContainer.codingPath)")
}
}
😀 소감 및 마무리
이제 서버 개발자분들에게 부담을 덜어줄 수 있는 스킬을 배운 것 같아서 좋다.
Encodable을 이런식으로 사용해본적이 없지만 그 상황이 오면 반대로 하면되니 크게 문제 없겠지 ??
출처
https://gist.github.com/stammy/0872636a4c740e8a2011e57eaf09bbff
'프로그래밍언어 > swift' 카테고리의 다른 글
GC vs ARC (1) | 2024.12.13 |
---|---|
클로저와 self (0) | 2024.10.23 |
swift 기본 타입 (0) | 2024.10.18 |
디스패치 학습하기 (4) [ Extension Dispatch ] (0) | 2024.08.26 |
디스패치 학습하기 (3) [ Value Type Dispatch, Protocol Dispatch ] (0) | 2024.08.25 |