🙈
(사진은 내가 그린 해저케이블 먹는 상어이다.)
오늘은 Codable에 대해 공부해보자
Swift에서 'Codable'이라는 단어는 '코딩 가능하다'는 뜻이다.
그니깐 뭔가를 코드로 바꾸거나 코드에서 뭔가를 만들어낼 수 있다는 의미다.
대표적으로 웹에서 많이 쓰이는 데이터 형식인 JSON을 Swift의 인스턴스로 변환하거나, 반대로 Swift의 인스턴스를 JSON으로 변환하는 데에 사용이 된다.
Codable은 사실 두 가지 일을 하는 두 개의 프로토콜, Encodable과 Decodable의 조합이다. 이 두 프로토콜은 데이터 변환에 관한 거다.
Encodable: 이건 Swift의 데이터 타입을 다른 형식으로 변환해주는 역할을 한다. 예를 들어, 내가 Swift에서 만든 어떤 클래스나 구조체가 있고, 이걸 JSON 형식으로 바꾸고 싶다면, 그 클래스나 구조체에 Encodable 프로토콜을 적용하면 된다. 그러면 Swift의 데이터를 JSON으로 쉽게 바꿀 수 있다!
Decodable: 이건 반대로, 외부의 데이터 형식을 Swift의 데이터 타입으로 변환해주는 역할을 한다. 만약 내가 JSON 데이터를 받았다고 해보자. 이 JSON 데이터를 Swift에서 사용할 수 있는 형태, 예를 들어 클래스나 구조체로 바꾸고 싶다면, 그 클래스나 구조체에 Decodable 프로토콜을 적용하면 된다. 그럼 JSON을 Swift 타입으로 변환할 수 있다!
그래서 Swift에서 Codable을 사용하면, 너의 Swift 코드와 외부 데이터 형식 사이에서 데이터를 쉽게 주고받을 수 있게 된다. 이게 특히 API를 사용할 때 유용한데, API에서 데이터를 받아 Swift 객체로 만들거나, Swift 객체를 JSON으로 바꿔서 API에 보낼 때 사용된다.
간단히 말해서, Codable은 Swift 데이터와 JSON 같은 외부 데이터 형식 사이를 쉽고 빠르게 오갈 수 있게 해주는 도구라고 할 수 있다.
만약에 서버에서 다음과 같은 JSON 문자열을 받았다고 해보자
여기에는 'author', 'quote', 'like'라는 세 개의 키와 각각의 값을 포함하고 있다.
var jsonData = """
{
"author": "Mentor",
"quote": "어디까지 디버깅 하셨나요?",
"like": 2132
}
"""
이렇게 다 String으로 오는데, 우리가 아래와 같이 이 데이터를 받을 준비를 할 수 있겠따.
그 다음에는 내가 쓸 데이터를 만들어보자. Quote라는 구조체에 Codable 프로토콜을 채택하고 있다. 그러면 JSON 데이터를 이 구조체 형태로 변환할 수 있다.
구조체 내에는 'quote', 'author', 'likeCount'라는 세 개의 프로퍼티가 있다.
struct Quote {
let quote: String
let author: String
let likeCount: Int
}
그러니까 우리는 서버에서 받은 데이터를 우리의 코드에서도 사용할 수 있게 만들어야 한다. String 타입을 내가 사용하는 데이터인 Quote 타입으로 변환해야 한다.
그래서 우선 내가 사용할 데이터에 Codable 프로토콜을 사용하게 해주고,
Quote 구조체 안에있는 프로퍼티가 JSON의 데이터와 동일한지 비교해보자.
quote, author는 그대로인데, like는 나는 likeCount라는 변수에 담고 싶다. 게다가 이건 Int 타입으로 사용할거다.
그렇다면 Codable의 부하직원(?)인 CodingKeys를 만들어준다.
이 열거형은 CodingKey 프로토콜을 채택하고 있으며, JSON의 키와 구조체의 프로퍼티를 매핑하는 역할을 한다. 예를 들어, JSON 데이터의 'like' 키는 구조체의 'likeCount' 프로퍼티와 연결되어 있다.
CodingKeys 열거형을 사용하면 내가 사용할 변수와 JSON의 값이 달라도, 나의 데이터인 Quote 구조체에서 사용할 수 있다.
아래는 이걸 사용한 코드 예시이다.
struct Quote: Codable {
let quote: String
let author: String
let likeCount: Int
// 애플이 미리 정의해둠 모든 경우 다 써야됨
enum CodingKeys: String, CodingKey {
case quote = "quote"
case author = "author"
case likeCount = "like"// 열거형의 rawvalue -> 하나씩 매칭해줌
}
}
내가 사용할 데이터인 Quote 구조체에 Codable 프로토콜을 채택해주었고,
키가 달라서 오류가 발생할 경우를 대비하여 CodingKeys로 내 데이터와 서버의 JSON 데이터를 짝을 맞춰주었다.
이제 함수를 통해 JSON 문자열을 Quote 구조체 타입으로 변환해보자.
이 함수는 먼저 jsonData 문자열을 Data 타입으로 변환한 후, JSONDecoder를 사용하여 이 데이터를 Quote 타입의 인스턴스로 변환한다.
func myDecoding() {
guard let data = jsonData.data(using: .utf8) else {
print("error")
return
}
// do try catch문 -> if else랑 비슷해
do { // 시도해
let value = try JSONDecoder().decode(Quote.self, from: data)
dump(value)
} catch { // 안되면
print(error)
}
}
이제 키가 다를 경우에 발생하는 오류를 해결하는 방법에 대해 알아보자.
크게 두 가지가 있는데,
하나는 구조체의 프로퍼티를 Optional 타입으로 선언하는 것이다.
struct Quote 안에 값들을 let quote: String? 요런식으로.
근데 이러면 에러는 안나도, 키가 다른 값들이 nil로 들어온다. 그러면 안되겠찌.
그래서 우리는 다른 방법인 CodingKeys 열거형을 사용한거다.
myDecoding()을 호출하면 아래와 같은 결과값이 출력된다.
- quote: "어디까지 디버깅 하셨나요?"
- author: "Mentor"
- likeCount: 2132
우리가 원하는 타입으로 잘 출력이 된 걸 볼 수 있다.
이런걸 어려운 말로 디코딩 전략이라고 한단다.
응용 개념으로는 CodingKey / init / decodeIfPresent / SnakeCase 가 있다.
얘네는 Swift의 Codable 프로토콜을 사용할 때 중요한 역할을 한다.
CodingKey: CodingKey는 프로토콜이며, JSON의 키와 Swift 구조체나 클래스의 프로퍼티를 매핑하는 데 사용된다. Codable을 구현하는 구조체나 클래스 내부에서, CodingKeys라는 이름의 열거형을 선언하고 CodingKey 프로토콜을 채택하여 사용한다. 이 열거형에는 JSON 키와 매칭될 프로퍼티의 이름을 지정한다. 예를 들어, JSON의 'first_name' 키를 Swift의 'firstName' 프로퍼티와 연결하고 싶다면, CodingKeys 열거형 안에 case firstName = "first_name"
같은 식으로 정의한다.
-> 위에서 우리가 사용해본 프로토콜이다.
init(from decoder: Decoder): 이것은 Decodable 프로토콜의 일부로, 사용자 정의 디코딩 로직을 구현하는 데 사용된다. 기본적으로 Swift는 Codable을 채택한 타입에 대해 자동으로 디코딩을 처리하지만, 더 복잡한 데이터 구조나 특수한 로직이 필요한 경우에는 init 메서드를 직접 구현할 수 있다. 이 메서드 내에서는 Decoder 인스턴스를 사용하여 JSON의 각 키에 해당하는 데이터를 추출하고 적절히 변환하여 인스턴스의 프로퍼티에 할당한다.
만약에~ JSON 데이터 내에 복잡한 구조가 있거나 특별한 디코딩 로직이 필요하다면, 이 메서드를 사용해야 한다. 예를 들어, JSON 내부에 중첩된 객체가 있고, 이 객체를 특별한 방식으로 디코딩하고 싶다면 아래와 같이 사용할 수 있다:
struct Quote: Codable {
let quote: String
let author: String
let likeCount: Int
enum CodingKeys: String, CodingKey {
case quote, author, likeCount = "like"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
quote = try container.decode(String.self, forKey: .quote)
author = try container.decode(String.self, forKey: .author)
likeCount = try container.decode(Int.self, forKey: .likeCount)
// 여기서 복잡한 디코딩 로직을 추가할 수 있음
}
}
음 복잡하군
decodeIfPresent
메서드를 사용하면, 해당 키가 JSON 데이터에 존재하지 않거나 값이 null일 때 오류가 발생하는 대신에, 선택적(Optional) 프로퍼티에 nil을 할당할 수 있다. 이 방법은 JSON 데이터가 불완전하거나 일부 데이터가 누락될 가능성이 있을 때 유용하다.JSON에서 어떤 값이 옵셔널일 경우 이 메서드를 사용할 수 있다. 예를 들어, likeCount가 JSON에 없을 수도 있다면,
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
quote = try container.decode(String.self, forKey: .quote)
author = try container.decode(String.self, forKey: .author)
likeCount = try container.decodeIfPresent(Int.self, forKey: .likeCount) ?? 0
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let value = try decoder.decode(Quote.self, from: data)
음 이런걸 언제써보나 싶지만 나중에 쓸 때 어디서 들어봤따 싶으면 좋겠는 마음으로 글을 마무리한다.
🙈