안녕하세요 피칸파이🥧 입니다. 이제는 인사를 계쏙 해야겠네요
Sopt 에서 서버 통신을 위해 Alamofire 를 세미나에서 배웠습니다. 서버 통신은 작년 애플 아카데미에서 UrlSession 으로 오픈API 를 다룰 때 잠깐 써보고 말아서 통신 자체가 낯설었는데요. 뭐 서버를 제대로 공부한적도 없었기에 구글을 오가며 봤던 얕은 통신 지식 밖에 없던 차에 encoding 과 decoding 에 대해서 조금 더 공부하고 싶었습니다.
Codable 과 CodingKeys ...!
먼저 Codable 부터 알아봅시다!
Codable: A type that can convert itself into and out of an external representation*.
typealias Codable = Decodable & Encodable
여기서 외부 표현 (external representation) 은 JSON 이라고 생각하시면 될 것 같네요.
Codable 은 Protocol 로 Class Struct Enum 에서 채택 가능하고,
또 채택을 해서 만든 Object 를 Serialize, Deserialize 를 할 수 있습니다.
궁금함이 생겼다. 네트워크 통신에서 Serialize 는 뭘까?? 그래서 찾아보았습니다..
Serialize: 프로그램의 Object 에 담긴 데이터를 어떤 외부 파일에 write 를 하거나, 전송하는 것
=> 상대 호스트와 데이터를 주고 받는 데이터의 타입이 우리가 사용하는 기본 타입인 Int, String 일 수 있지만 다른 타입일 수 있다. 이런 상태로 데이터를 전송하면 문제가 생길 수 있다. 그렇기 때문에 데이터를 그대로 전송하지 않고, 전송할 데이터들을 하나의 버퍼에 연속된 단위로 길게 나열하여 연속으로 저장하는 방식을 Serialization 이라고 한다.
Deserialize 는 그 반대로 작동하여 데이터를 복구하는 것이다.
+ 그렇다면 Serialize 와 Encode 는 어떻게 다른까요?
Encoding 은 데이터가 다른 형식의 무언가로 바뀌는 것을 말하기 때문에, Serialization 은 그 Encoding 시에 내부에서 작동하는 하나의 기능이라고 생각하면 될 것 같습니다. 부분 집합이라고 해야하지 않을까요?
그래서 Object 를 Serialization(Encoding) 하기 위해서 이 Codable 을 채택해야 한다고 간단하게 생각하면 됩니다.
그렇다면 어떻게?
먼저 순서를 살펴보겠습니다.
(인코딩)
1. 데이터 인코딩을 위한 struct 만들기 ( Codable )
2. 내 데이터를 JSON Object 로 만들기 ( JSONEncoder() )
3. 인코딩될 JSON Object 의 옵션 정하기 ( .~EncodingStrategy )
4. Object 를 String 값으로 만들기
---------------------------
(디코딩)
5. 받은 data 를 디코딩하기 ( JSONDecoder() )
5-1. 디코딩을 위한 struct 만들기 ( CodingKeys )
[인코딩]
1. 내가 서버에 올릴 (우리가 설계한 모델과 같게...) 모델을 Codable 한 struct 를 통해 선언한다.
// 내가 만들고 싶은 JSON
"""
"name" : "심규보",
"sopt_part" : "iOS",
"is_yb" : true,
"is_couple" : false
"age" : 77,
"birth_date" : "2023-05-08T05:46:50+0000"
"""
// 형식이 같게 만들기 (feat. SnakeCase)
struct EncodingSotpPerson: Codable {
var name: String
var sopt_part: String
var is_yb: Bool
var is_couple: Bool
var age: Int
var birth_date: Date
}
[Swift 는 CamelCase 를 대부분 쓰지만, 다른 파트에서 넘어온 이런 변수명들이 똑같이 CamelCase 라는 보장이 없어, 일부러 SnakeCase 를 사용했습니다.]
2. 자 이제 해당 구조체로 JSON 으로 만들어봅시다!
- JSONEncoder 의 인스턴스 중 outputFormatting 프로퍼티는 해당 output 이 어떻게 표현될건지를 정하는데요.
우리가 생각하는 형식의 JSON 형태를 만들고 싶다면, (물론 서버에 올리는건 굳이 이걸 사용하지 않아도 되겠..죠?)
JSONEncoder().outputFormatting = .prettyPrinted
를 하면 된답니다. 암무튼 전체 코드를 보자면,
private func encodeWithSopt() {
let QBO = EncodingSotpPerson(name: "SKB",
sopt_part: "iOS", is_yb: true,
is_couple: false, age: 77,
birth_date: Date())
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
jsonData = try? encoder.encode(QBO)
guard let jsonData = jsonData,
let jsonString = String(data: jsonData, encoding: .utf8) else { return }
self.jsonData = jsonData
self.jsonString = jsonString
print("------------")
print(jsonString)
print("------------")
}

다음과 같은 결과물이 나옵니다.
3. 다른건 다 오케이! 근데 birth_date 가 이상한 숫자로 나오죠? 분명 birth_date의 데이터는 Date 인데 말이죠?
그것은 바로 우리가 선언한 JSONEncoder 의 인스턴스의 Date 의 형식을 정해주는
.dateEncodingStrategy 프로퍼티가 기본값이 .deferredToDate 이어서 그렇습니다.
- deferredToDate 는 무엇일까요?? 분명 값이 705237801... 나오는걸 보니 Seconds 같긴 한데...
그래서 찾아보니 Apple 시스템 내에서 편하게 쓰기 위해 만든 형식으로 2001 년부터의 시간이라고 합니다.
"The .deferredToDate format is Apple’s own date format, and it tracks the number of seconds and milliseconds since January 1st 2001."
근데 웃긴건 이게 공식 다큐먼트에서는 이렇게 써있지도 않습니다. 허허... 아무튼 이 형식은 apple 내에서만 사용하는게 아니면 못 쓸 용도네요.
그래서 이제는 우리가 Date 에 사용하는 그 익숙한 방식으로 만들어 봅시다.
바로 .iso8601 이란 형식인데요. 익숙하죠?
그래서 우리는 이걸 사용할 겁니다. 다음 코드를 선언해줍시다.
encoder.dateEncodingStrategy = .iso8601

+ 추가적으로 이 Date 형식에 대해서 Custom 을 하여 사용할 수도 있는데, 그것은 DateFormatter 를 사용하여 지정하면 된답니다!
4. 우리가 위에서 JSON Object 를 만들었잖아요? 이제 그 data 를 String 으로 바꿔봅시다.
let jsonString = String(data: jsonData, encoding: .utf8)
이때, 우리는 거의 대부분 utf-8 을 사용하여 인코딩을 하기 때문에, JSONData 를 utf8 로 인코딩하여 String 으로 변환하겠다 라는 뜻입니다.
[디코딩]
우리는 보통 앱을 만들면서 data 를 get 을 많이하죠. 상대적으로?
즉, 파싱을 더 많이 하기 때문에 Decoding 이 더 중요한 셈이죠.
그 전에 먼저!
우리가 위에서 인코딩을 위해 만든 struct 를 잊어버리고 순전히 받아온 data 만 생각을 해봅시다.

흠!🤔 우리 iOS 는 보통 CamelCase 를 쓰는데 서버에서 내려온 Key들은 SnakeCase 를 쓰네!
어쩃든 우리는 CamelCase 를 사용해서 받아올 struct 를 짜보자!
struct DecodingSoptPerson: Codable {
var name: String
var part: String
var isYB: Bool
var isCouple: Bool
var age: Int
var birthDate: Date
}
5. 그리고 나서 ! 우리는 이제 받은 데이터를 가지고 Decoding 을 해봅시다!
똑같이 우리가 Encoding 을 했던 그 반대로 하면 됩니다.
Encoding -> Decoding 으로!
private func decodeWithSopt() {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let decodedData = try! decoder.decode(EncodingSoptPerson.self, from: jsonData)
print("📍: \(decodedData)")
}
좋아 이제 date 도 따로 만져줬으니깐 문제가 없겠지?! 어?? 왜이러지!

"No value assocaited with key \part\"
Key 값이 달라서 찾을 수가 없다 라는 뜻과 같습니다.
- 이유는 이렇습니다.
5-1. Key 를 기준으로 디코딩을 하게 되는데,
JSON 의 part 에 해당하는 Key 는 "sopt_part" 이고
우리 struct 에서는 "part" 입니다.
다릅니다. 그렇기 때문에 이렇게 디코딩 에러가 생기는 겁니다.
그러면 우리가... 우리가... 자존심을 굽히고 SnakeCase 를 써야하느냐?
아니죠 그럴 순 없죠.
그래서 사용할 수 있는게 바로 CodingKey 라는 protocol 입니다.
CodingKey: A type that can be used as a key for encoding and decoding.
인코딩 및 디코딩을 위한 키로 사용할 수 있는 타입 입니다.
이렇게!
struct DecodingSoptPerson: Codable {
var name: String
var part: String
var isYB: Bool
var isCouple: Bool
var age: Int
var birthDate: Date
enum CodingKeys: String, CodingKey {
case name, age
case part = "sopt_part"
case isYB = "is_yb"
case isCouple = "is_couple"
case birthDate = "birth_date"
}
}
📍
내가 사용할 model struct 에서 part 부분을 JSON 의 Key 중 "sopt_part" 로 인식할게.
내가 사용할 model struct 에서 isYB 부분을 JSON 의 Key 중 "is_yb" 로 인식할게.
...
라는 겁니다.
그래서 우리는 이렇게 세팅을 하면...! 서버에서 넘어오는건 넘어오는대로, CamelCase 는 그대로 지키고!!
그래서 우리는 이렇게 Codabel 과 CodingKeys 에 대하여 알아보았고...
조금은 익숙해져서 나중에 편하게 쓰는 날이 오길 바래야겠죠?
끝!
'⌨️ UIKit' 카테고리의 다른 글
| UISrollView 그냥 이렇게 써보자. [예시 코드 딱!] (0) | 2023.05.18 |
|---|---|
| SwiftLint 를 Xcode에 적용시키기 (이슈 및 헷갈렸던 점도 포함!) (0) | 2023.05.15 |
| UICollectionView 를 통해 애니메이션이 있는 Segmented TapView 구현해보기 (0) | 2023.05.02 |
| UIKit UICollectionView 에서 초기값이 선택되어 있게 만들기 - PreSelected (0) | 2023.05.01 |
| [UIKit] UIPageViewController 에서 Swipe 기능 빼기 + 버튼으로 넘기기 (0) | 2022.12.29 |
댓글