"Make your data types encodable and decodable for compatibility with external representations such as JSON."
JSON과 같은 형식으로 통용할 수 있도록 데이터 타입을 인코더블, 디코더블하게 만드는 것에 대해서 이야기합니다.
네트워크 연결, 데이터 저장, API와 서비스에 데이터 전달과 같은 작업은 빈번하게 사용하는 프로그래밍 작업입니다. 이와 같은 작업들은 데이터가 오고 가는 동안 중간에서 매개할 수 있는 파일 형식으로 encode 혹은 decode되어야 할 때가 있습니다.
스위프트 표준 라이브러리는 데이터를 인코딩, 디코딩하기 위한 표준화된 접근방식을 제공합니다. 이 Encodable
과 Decodable
프로토콜을 수행함으로써 만들어진 커스텀 타입에 대해 이 접근방식을 사용할 수 있습니다. 이 프로토콜을 채택하면 Encoder
, Decoder
프로토콜을 사용함으로써 JSON과 같은 형식의 표현으로 인코딩하거나 디코딩할 수 있도록 합니다. 인코딩과 디코딩 모두를 지원하려면 Codable
프로토콜을 채택하면 됩니다. 이러한 시작이 타입을 코더블하도록 합니다.
타입을 코더블할 수 있도록 만드는 가장 쉬운 방법은 이미 Codable
인 타입을 프로퍼티에 선언하는 것입니다. 이러한 타입은 표준 라이브러리 타입을 포함하고 있으며, 스트링, 인트, 더블, 데이터, URL과 같은 타입입니다. 이들은 Codable
에 순응하도록 선언하는 것을 통해 자동적으로 코더블해집니다.
이름과 설립 연도라는 속성을 갖는 랜드마크 구조체에 대해 생각해보겠습니다.
struct Landmark {
var name: String
var foundingYear: Int
}
Codable
상속을 통해 자동적으로 인코더블과 디코더블로부터 프로토콜 요구사항을 충족합니다.
struct Landmark: Codable {
var name: String
var foundingYear: Int
// Landmark now supports the Codable methods init(from:) and encode(to:),
// even though they aren't written as part of its declaration.
}
커스텀 타입에 대한 Codable
채택은 내장된 데이터 포맷에 동기화할 수 있도록 하고, 인코더와 디코더로부터 제공되는 특정 포맷으로도 동기화할 수 있도록 합니다. 예를 들어 랜드마크 구조체는 PropertyListEncoder
, JSONEncoder
클래스로 인코딩될 수 있으며, 이 작업은 property list나 JSON을 다루는 코드를 포함하지 않더라도 인코딩될 수 있습니다.
코더블한 다른 커스텀 타입 역시 같은 원칙이 적용됩니다. 프로퍼티가 코더블하다면, 모든 커스텀 타입이 Codable
합니다.
아래 예시는 location 속성이 랜드마크 구조체에 추가될 때 자동적으로 Codable
프로토콜에 순응하는 것을 보여줍니다.
struct Coordinate: Codable {
var latitude: Double
var longitude: Double
}
struct Landmark: Codable {
// Double, String, and Int all conform to Codable.
var name: String
var foundingYear: Int
// Adding a property of a custom Codable type maintains overall Codable conformance.
var location: Coordinate
}
배열, 사전, 옵셔널처럼 내장된 타입 또한 코더블 타입을 포함하고 있는 경우라면 Codable
에 순응합니다. Coordinate 인스턴스를 랜드마크 구조체에 추가할 수 있고, 구조체 전체는 계속 Codable
합니다.
내장된 코더블 타입을 여러 가지 추가한 경우가 아래 예시입니다.
struct Landmark: Codable {
var name: String
var foundingYear: Int
var location: Coordinate
// Landmark is still codable after adding these properties.
var vantagePoints: [Coordinate]
var metadata: [String: String]
var website: URL?
}
어떤 경우에는 인코딩과 디코딩 두 가지 중 한 가지만 필요한 경우도 있습니다. 예를 들어 어떤 앱은 원격 네트워크 API를 호출하기만 할 수도 있습니다. 즉 같은 타입으로 디코딩할 필요가 없는 경우입니다. 데이터 인코딩만 필요한 경우 Encodable
만 채택하면 되며, 반대로 디코딩만 필요하다면 Decodable
만 채택하면 됩니다.
아래 예시가 Encodable
과 Decodable
중 한 가지만 채택하는 것을 보여줍니다.
struct Landmark: Encodable {
var name: String
var foundingYear: Int
}
struct Landmark: Decodable {
var name: String
var foundingYear: Int
}
Codable
타입은 코딩키 프로토콜에 순응하는 코딩키 이름의 중첩 열거형을 선언할 수 있습니다. 이 열거형이 있다면 코더블 타입의 인스턴스가 인코딩 혹은 디코딩될 때 열거형의 케이스들이 반드시 포함되어야 하는 authoritative list of property를 제공합니다. 열거형 케이스의 이름은 타입에서 정의한 속성에 상응하는 이름과 대응할 수 있도록 해야 합니다.
디코딩 인스턴스를 생성할 때 디코딩에 필요하지 않은 속성이라면 그 속성은 코딩키에서 제외할 수 있습니다. 인코딩 역시 마찬가지입니다. 코딩키에서 제외된 속성은 디코더블 혹은 코더블에 자동으로 순응할 수 있도록 타입을 포함하기 위한 기본값을 요구하게 될 것입니다.
만약 데이터 포맷에 동기화된 키가 정의한 데이터 타입의 프로퍼티 이름과 일치하지 않는다면, 코딩키 열거형에 raw-value 타입의 스트링 alternative 키를 생성하면 됩니다. 열거형 케으시에 raw value로 사용하는 스트링은 인코딩과 디코딩하는 과정에서 키 이름이 됩니다.
열거형의 케이스 이름과 raw value 사이의 관계는 동기화된 이름, punctuation, capitalization이 아닌 Swift API Design Guideline을 따르는 데이터 구조에 이름짓는 것을 가능하도록 합니다.
아래 예시는 랜드마크 구조체에서 이름과 설립연도 프로퍼티의 alternative key 활용을 보여줍니다.
struct Landmark: Codable {
var name: String
var foundingYear: Int
var location: Coordinate
var vantagePoints: [Coordinate]
enum CodingKeys: String, CodingKey {
case name = "title"
case foundingYear = "founding_date"
case location
case vantagePoints
}
}
인코딩된 형식의 구조체와 정의한 구조체의 타입이 다르다면, 인코딩과 디코딩 로직을 정의하기 위해 커스텀 Encodable
과 Decodable
을 만들 수 있습니다.
아래 예시는 addtionalInfo
컨테이너 내부에 중첩된 elevation 속성을 지원하는 확장된 형태의 좌표 구조체입니다.
struct Coordinate {
var latitude: Double
var longitude: Double
var elevation: Double
enum CodingKeys: String, CodingKey {
case latitude
case longitude
case additionalInfo
}
enum AdditionalInfoKeys: String, CodingKey {
case elevation
}
}
좌표 타입의 인코딩된 형태가 중첩된 정보의 second level을 포함하기 때문에 타입의 Encodable
, Decodable
채택은 특정 수준에서 사용되는 완성된 코딩키의 리스트인 두 가지 열거형을 사용합니다.
아래 예시에서 좌표 구조체는 required initializer
를 사용하는 것을 통해 Decodable
프로토콜에 순응하도록 확장된 형태로 만들어져 있습니다.
extension Coordinate: Decodable {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
latitude = try values.decode(Double.self, forKey: .latitude)
longitude = try values.decode(Double.self, forKey: .longitude)
let additionalInfo = try values.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
elevation = try additionalInfo.decode(Double.self, forKey: .elevation)
}
}
이니셜라이저 파라미터로 받는 Decoder
인스턴스에서 메소드를 사용함으로써 좌표 인스턴스에 있습니다. 좌표 인스턴스에 있는 두 가지 프로퍼티는 스위프트 표준 라이브러리에서 제공하는 keyed container API에 의해 초기화됩니다.
아래 예시는 좌표 구조체가 어떻게 Encodable
프로토콜을 순응할 수 있도록 확정되는지를 보여줍니다. required
메소드를 사용하고 있습니다.
extension Coordinate: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(latitude, forKey: .latitude)
try container.encode(longitude, forKey: .longitude)
var additionalInfo = container.nestedContainer(keyedBy: AdditionalInfoKeys.self, forKey: .additionalInfo)
try additionalInfo.encode(elevation, forKey: .elevation)
}
}
이처럼 encode(to:)
메소드를 사용하는 것은 이전 예제에 비해 디코딩 오퍼레이션을 역전시킵니다.
인코딩과 디코딩 프로세스를 커스터마이징할 때 컨테이너 타입에 대한 더 많은 정보가 필요하다면 아래를 참고하시길 바랍니다.
KeyedEncodingContainerProtocol
https://developer.apple.com/documentation/swift/keyedencodingcontainerprotocol
UnkeyedEncodingContainer
https://developer.apple.com/documentation/swift/unkeyedencodingcontainer