프로그래밍 작업에 빠질 수 없는 것 바로 네트워크 통신입니다. 서버로 데이터를 보내고, 받기도 하고 Restful API에서는 빠질 수 없는 요소입니다. 그렇다면 어떠한 형식으로 데이터를 보내고 받아야 할까요?
먼저 인코딩이란 효율적인 전송을 위해 문자, 숫자 등등의 일련의 문자들을 특수한 형식으로 변환하는 프로세스입니다. 서버로 데이터를 보낼 때 서버에 맞는 형식으로 보내주어야 제대로 값이 보내지게 됩니다.
Swift에서는 Encodable이라는 프로토콜이 있습니다.
Encodable
프로토콜이기 때문에 class, struct, enum 어디에서도 채택 가능합니다.
보통의 경우에는 struct로 encoding하는 경우가 많기 때문에 다음과 같은 구조체를 하나 만들어보겠습니다.
struct Animal: Encodable {
let name: String
let age: Int
let weight: Double
}
구조체를 하나 만들었습니다. Animal 구조체는 3개의 프로퍼티가 있습니다. 이제는 Encodable 프로토콜을 채택했기 때문에 서버로 전송가능한 폼으로 변환이 가능한 Animal이 되었습니다.
이제 Animal구조체를 encoding하는 방법을 알아봅시다.
let cow = Animal(name: "소", age: 2, weight: 50.0)
let cowEncoding = try? JSONEncoder().encode(cow)
먼저 cow라는 객체를 하나 만들어줍니다.
이후 JSONEncoder()를 사용해 encode를 진행해줍니다.
💡 JSONEncoder()일반적으로, JSONEncoder
는 Encodable
프로토콜을 채택한 타입의 인스턴스를 JSON 데이터로 변환합니다. 위에서 언급한 Animal
구조체가 채택하고 있기 때문에, 이를 를 사용하여 JSON 데이터로 인코딩할 수 있었습니다.
그렇다면 encode는 어떤 메서드 일까요?
func encode<T>(_ value: T) throws -> Data where T : Encodable
T라는 Type의 value를 입력받고, Data형식을 반환하고 있습니다. 또한 throws로 에러를 암시하고 있습니다. 여기서 T는 Encodable 프로토콜을 채택해야만 합니다.
요약하자면 Encodable 프로토콜을 채택한 어떠한 타입을 입력받아, type이 일치한다면 Data로 변환하고 일치하지 않는다면 에러를 던지는 함수가 encode입니다.
에러도 처리하게 코드를 다음과 같이 수정하겠습니다.
let cow = Animal(name: "소", age: 2, weight: 50.0)
do {
let encoder = JSONEncoder()
let cowEncoding = try JSONEncoder().encode(cow)
print(String(data: cowEncoding, encoding: .utf8)!)
} catch {
print("Encode Error")
}
//결과: {"name":"소","age":2,"weight":50}
cowEncoding은 Data Type이기 때문에 print를 사용해 출력한다고 한들 00bytes로 출력되기 때문에 Swift에서 제공하는 생성자를 이용해 출력할 수 있습니다.
String(data: Data, encoding: String.Encoding)
좀 더 예쁜 출력결과를 원한다면 JSONEncoder를 수정해볼 수 있습니다.
let cow = Animal(name: "소", age: 2, weight: 50.0)
do {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let cowEncoding = try encoder.encode(cow)
print(String(data: cowEncoding, encoding: .utf8)!)
} catch {
print("Encode Error")
}
// 결과:
{
"name" : "소",
"age" : 2,
"weight" : 50
}
지금까지는 Encodable을 살펴보고 실제 encode까지 해봤습니다. Decodable은 무엇일까요?
서버로 보내기 위한 과정이 encoding 이였습니다. 반대의 경우도 있어야합니다. 즉 서버로부터의 응답을 받는다는 것은 decoding입니다.
struct Animal: Encodable, Decodable {
let name: String
let age: Int
let weight: Double
}
따라서 Animal type으로 서버의 응답이 올 때 Decodable 프로토콜을 채택해주면 됩니다.
여기서도 encode와 마찬가지로 decode라는 메서드가 보입니다.
decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable
Decodable 프로토콜을 채택하는 type T에 서버로 받은 data를 넣고, 이에 결과로 해당하는 type을 반환받고 있습니다. 반환하는 과정에서 오류가 생길 것을 예상해 throws가 붙은 모습입니다.
let decoder = JSONDecoder()
let cowDecoding = try! decoder.decode(Animal.self, from: cowEncoding)
print(cowDecoding)
//결과: Animal(name: "소", age: 2, weight: 50.0)
type에는 내가 서버로부터 받고자 하는 일치하는 타입을 적어줍니다. from에는 Data type이 들어와야합니다.
그 결과 Animal type의 값을 얻었습니다.
Swift에서는 Encodable, Decodable을 한번에 사용할 수 있게 Codable이라는 프로토콜 또한 존재합니다.
typealias Codable = Decodable & Encodable
Decodable과 Encodable 둘다 채택하고 있는 것을 앞으로 Codable이라고 부르겠다! 라고 나와있습니다.
마지막으로 CodingKey입니다.
서버에서 내려주는 값들의 이름이 저희가 예상한것마냥 예쁘게 내려오는 경우가 종종 있습니다. 이럴 때 이름을 원하는 값으로 설정해주는 방법입니다.
{
"age_i" : 2,
"weight_i" : 50,
"name_i" : "소"
}
다음과 같이 서버에서 값을 내려받았다고 가정합시다. 작성했던 변수이름들에 _i가 붙은 모습입니다.
struct AnimalResponse: Decodable {
let name: String
let age: Int
let weight: Double
enum CodingKeys: String, CodingKey {
case name = "name_i"
case age = "age_i"
case weight = "weight_i"
}
}
우선 원하는 프로퍼티 이름으로 수정 할 필요가 있습니다. 그다음 enum CodingKeys에 String과 CodingKey를 붙여줍니다. String은 case들에 String값을 붙이기 위함이고, CodingKey는 내려오는 이름을 해당 케이스 이름으로 수정해줄 수 있음을 의미합니다.
do {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let cowEncoding = try encoder.encode(cow)
print(String(data: cowEncoding, encoding: .utf8)!)
let decoder = JSONDecoder()
let cowDecoding = try! decoder.decode(AnimalResponse.self, from: cowEncoding)
print(cowDecoding)
} catch {
print("Encode Error")
}
//결과:
{
"age_i" : 2,
"weight_i" : 50,
"name_i" : "소"
}
AnimalResponse(name: "소", age: 2, weight: 50.0)
그 결과 내려오는 값의 이름과 다르게 직접 설정한 이름으로 수정되어 AnimalResponse에 매핑된 모습입니다.
지금까지 Encodable과 Decodable을 알아보았습니다. 매번 DTO를 작성할때마다 Codable로 퉁쳐서 의미도 모른체 사용하던 저에게 어떻게 사용해야하는지 배울 수 있었던 뜻깊은 시간이었습니다 :)