๐Ÿš€ iOS ๋„คํŠธ์›Œํ‚น ์ •๋ณตํ•˜๊ธฐ : CodingKeys / Custom ์ธ์ฝ”๋”ฉ๊ณผ ๋””์ฝ”๋”ฉ

a.veryยท2022๋…„ 9์›” 25์ผ
0

์ด๋ฒˆ ์‹œ๊ฐ„์—๋Š” key ์ด๋ฆ„์„ ์ปค์Šคํ…€ํ• ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” Coding Key ์™€ ์ง์ ‘ ์ธ์ฝ”๋”ฉ๊ณผ ๋””์ฝ”๋”ฉ์„ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

๊ทธ์ „์— ์™œ? ์ปค์Šคํ…€ ํ• ์ผ์ด ์ƒ๊ธธ๊นŒ์š”?
api ์‘๋‹ต ๋ฐ์ดํ„ฐ๊ฐ€ Camel Case ๊ฐ€ ์•„๋‹ˆ๋ผ snake case ๋ผ๋˜๊ฐ€ ๋‹ค๋ฅธ ํ˜•์‹์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ swift ์˜ ์ปจ๋ฒค์…˜์— ๋งž์ง€ ์•Š๋Š”๋ฐ ์š”๋Ÿฌํ•œ ๊ฒฝ์šฐ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์‹œํ•œ๋ฒˆ ์ž์„ธํžˆ ์„ค๋ช…ํ•˜๋ฉด!
์ผ๋ฐ˜์ ์œผ๋กœ ๋ฐ์ดํ„ฐ ํ†ต์‹ ์„ ์œ„ํ•œ ๋ชจ๋ธ์„ ๋งŒ๋“ค๋•Œ, Codable Protocol ์„ ์ฑ„ํƒํ•˜๋Š”๋ฐ์š”
์ด๋•Œ, ๊ตฌํ˜„ํ•˜๋ ค๋Š” ๊ตฌ์กฐ์ฒด ์†์„ฑ๊ณผ JSON Data ์˜ key๊ฐ’์ด ์ผ์น˜ํ•ด์•ผ ์ •์ƒ์ ์œผ๋กœ Decoding ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค
๊ทผ๋ฐ ๊ทธ๊ฒŒ ์‰ฝ์ง€ ์•Š์ฃ ..? ๊ทธ๋•Œ CodingKeys ๋ฅผ ์‚ฌ์šฉํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

Coding Keys

์• ํ”Œ ๊ณต์‹๋ฌธ์„œ์—์„œ๋Š” ์ธ์ฝ”๋”ฉ/๋””์ฝ”๋”ฉ์„ ์œ„ํ•œ ํ‚ค๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํƒ€์ž…์ด๋ผ๊ณ  ์ •์˜๊ฐ€ ๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿผ ์‚ฌ์šฉ๋ฒ•์„ ์•Œ์•„๋ด…์‹œ๋‹ค.

openweathermap api์˜ ๊ฒฐ๊ณผ ์ผ๋ถ€์ž…๋‹ˆ๋‹ค! ๋”ฑ๋ด๋„ Swift ์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ๊ทธ๋ ‡์ฃ ?

์ด๋Ÿฌํ•œ ๊ตฌ์กฐ์ฒด๋ฅผ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๊ณ ์žํ•ฉ๋‹ˆ๋‹ค.

struct Temp: Codable {
  let temp: Double
  let feelsLike: Double
  let minTemp: Double
  let maxTemp: Double
}

๊ทธ๋Ÿผ ์•„๋ž˜์˜ ๋‹จ๊ณ„๋ฅผ ๊ฑฐ์น˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
1. mapping ๋  ์†์„ฑ์„ CodingKeys ์—ด๊ฑฐํ˜•์— ์ถ”๊ฐ€ํ•œ๋‹ค
2. CodingKeys ์—ด๊ฑฐํ˜•์€ CodingKey ํ”„๋กœํ† ์ฝœ์„ ์ฑ„ํƒํ•œ๋‹ค.
3. mapping ์ด ๋‹ฌ๋ผ์ง€๋Š” ์†์„ฑ์˜ rawValue ๋กœ ํ•„์š”ํ•œ JSON Data์˜ ํ‚ค ๊ฐ’์„ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค.

struct Temp: Codable {
  let temp: Double
  let feelsLike: Double
  let minTemp: Double
  let maxTemp: Double

  enum CodingKeys: String, CodingKey {
    case temp
    case feelsLike = "feels_like"
    case minTemp = "temp_min"
    case maxTemp = "temp_max"
  }
}

Encode and Decode Manually

์ด๋ˆ„๋‹˜์˜ ๋ธ”๋กœ๊ทธ๋ฅผ ์ฐธ๊ณ ํ–ˆ์Šต๋‹ˆ๋‹ค

๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒฝ์šฐ ์ธ์ฝ”๋”ฉ ๋ฐ ๋””์ฝ”๋”ฉ ๊ณผ์ •์„ ์ง์ ‘ ๊ตฌํ˜„ํ•˜์—ฌ ์ž์‹ ๋งŒ์˜ ์ธ์ฝ”๋”ฉ ๋ฐ ๋””์ฝ”๋”ฉ ๋กœ์ง์„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ์ธ์ฝ”๋”ฉ ํ˜น์€ ๋””์ฝ”๋”ฉ ์‹œ์ ์— ๊ฐ’์„ ๊ฒ€์ฆํ•˜๊ฑฐ๋‚˜ ์ œ์•ฝ์„ ์ฃผ๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ
  • ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณต๋˜๋Š” ์ธ์ฝ”๋”ฉ ๋ฐ ๋””์ฝ”๋”ฉ ๊ฐ์ฒด์— ์ œ๊ณต๋˜๋Š” ์†์„ฑ์ด๋‚˜ CodingKeys๋กœ๋Š” ์›ํ•˜๋Š” ๊ฒฐ๊ณผ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ

Encoding

struct Member: Codable {
    var name: String
    var age: Int
    var job: String?

    func encode(to encoder: Encoder) throws {
        var container = encoder.contatiner(keyedBy: CodingKeys.self)
        
        try container.encode(name, forKey: .name)
        guard (20...30).contains(age) else { throw EncodingError.invalidRange }
        try container.encode(age, forKey: .age)
        try container.encodeIfPresent(job, forKey: .job)
    }
}
  1. encoding ์ฒ˜๋ฆฌ๋œ ๋ฐ์ดํ„ฐ๋Š” encoder ๋‚ด๋ถ€์— container ํ˜•ํƒœ๋กœ ์ €์žฅ๋˜๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๋จผ์ € ๊ฐ€์ ธ์˜จ๋‹ค.
    • containter : CodingKey ์—ด๊ฑฐํ˜•์— ์กด์žฌํ•˜๋Š” Key ๊ฐ’๊ณผ ์†์„ฑ์„ ๋งคํ•‘ํ•  ์ˆ˜ ์žˆ๋‹ค.
  2. container.encode๋ฅผ ํ†ตํ•ด ๋‚ด๋ถ€ ์†์„ฑ์„ Key๊ฐ’๊ณผ ๋งคํ•‘์‹œ์ผœ ์ €์žฅ
  3. guard ๋ฌธ์„ ํ†ตํ•ด ๋ฒ”์œ„๋ฅผ ํ™•์ธํ•˜๊ณ  ๋ฒ”์œ„์— ํ•ด๋‹นํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ ์—๋Ÿฌ๋ฅผ ๋ฐฉ์ถœ
    • ์ด๋•Œ, EncodingError.invalidRange๋Š” ์ง์ ‘ ์„ ์–ธํ•œ Error์ด๋‹ค.
  4. optional ๊ฐ’์˜ ๊ฒฝ์šฐ encodeIfPresent ์„ ์‚ฌ์šฉ

Decoding

struct Member: Codable {
    var name: String
    var age: Int
    var job: String?

    init(from decoder: Decoder) throws {
        let container = decoder.contatiner(keyedBy: CodingKeys.self)

        name = try container.decode(String.self, forKey: .name)
        age = try container.decode(Int.self, forKey: .age)
        guard (20...30).contains(age) else { throw EncodingError.invalidRange }
        job = try container.decodeIfPresent(String.self, forKey: .job)
    }
}
  1. encoder ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ container ๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
  2. container.decode๋ฅผ ํ†ตํ•ด ์›ํ•˜๋Š” key์˜ ๊ฐ’์„ ์›ํ•˜๋Š” ํƒ€์ž…์œผ๋กœ ๋ฐ›์•„์˜ต๋‹ˆ๋‹ค.
  3. guard ๋ฌธ์„ ํ†ตํ•ด ๋ฒ”์œ„๋ฅผ ํ™•์ธํ•˜๊ณ  ๋ฒ”์œ„์— ํ•ด๋‹นํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ ์—๋Ÿฌ๋ฅผ ๋ฐฉ์ถœ
    • ์ด๋•Œ, EncodingError.invalidRange๋Š” ์ง์ ‘ ์„ ์–ธํ•œ Error์ด๋‹ค.
  4. optional ๊ฐ’์˜ ๊ฒฝ์šฐ decodeIfPresent ์„ ์‚ฌ์šฉ

Container Type

์ธ์ฝ”๋”ฉ๊ณผ ๋””์ฝ”๋”ฉ์„ customizing ํ• ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ์ปจํ…Œ์ด๋„ˆ ํƒ€์ž…์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€
KeyedEncodingContainerProtocol ๋ฐ UnkeyedEncoding Container ๋ฅผ ์ฐธ๊ณ 

References

https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types
https://inuplace.tistory.com/1224?category=1058164
https://tech.burt.pe.kr/swift/codable/encoding-decoding-custom-type

profile
๐Ÿššchanhee-jeong.tistory.com ๐Ÿš€ github.com/chaneeii/iOS-Study-Log

0๊ฐœ์˜ ๋Œ“๊ธ€