Jiseob Kim

iOS Developer

Swift - Property Wrapper (feat. Codable 2ํŽธ)

27 Jun 2021 » Swift


์ง€๋‚œ ํŽธ์— ์ด์–ด์„œ ์ง„ํ–‰!


์ง€๋‚œํŽธ ๋งˆ์ง€๋ง‰์— ๋‹จ์ ์„ ์ ์–ด์ฃผ๋ฉฐ ๋งˆ๋ฌด๋ฆฌ๋ฅผ ์ง€์—ˆ์—ˆ๋‹ค.

์ด๋ฒˆํŽธ์€ ๊ทธ ๋‹จ์ ์„ ๋ณด์™„ํ•˜๊ธฐ ์œ„ํ•œ ํŽธ.

๋ฐฐ์šฐ๋Š” ๋‚œ์ด๋„๊ฐ€ ์ƒ๊ฐ๋ณด๋‹ค ๋†’์•˜๋‹ค.


์ง€๋‚œํŽธ์— ์ ์€ ๋‹จ์ ์˜ ์ผ๋ถ€๋ถ„์ด๋‹ค

โ€ฆ ํ•˜์ง€๋งŒ, isHidden์ด๋ผ๋Š” ๊ฐ’์ด ์—†์„ ๊ฒฝ์šฐ, ๋ชจ๋ธ์— ํ•ด๋‹น ๊ฐ’์„ ์˜ต์…”๋„์ฒ˜๋ฆฌ ํ•ด์ฃผ๋”๋ผ๋„ ์ด ์ฝ”๋“œ๋Š” throwํ•˜๊ฒŒ ๋œ๋‹ค. โ€ฆ ์‹ค์ œ๋กœ ์„œ๋ฒ„์—์„œ๋„ Request์— ๋”ฐ๋ผ Response๊ฐ€ ๋‹ฌ๋ผ์งˆ ๊ฐ€๋Šฅ์„ฑ์€ ์กด์žฌํ•œ๋‹ค. ๊ทธ๋ ‡๊ธฐ์— ์ด ๋‹จ์ ์€ ์น˜๋ช…์ ์ด๋ผ ์ƒ๊ฐ๋œ๋‹ค.



์ฐจ๊ทผ ์ฐจ๊ทผ ํ•˜๋‚˜์”ฉ ํ’€์–ด๋‚˜๊ฐ€๋ณด์ž

์šฐ์„ ์€ String์— ๋Œ€ํ•ด์„œ๋งŒ ๋ณด๊ณ  Generic์„ ์ด์šฉํ•ด ๋ฒ”์œ„๋ฅผ ๋„“ํ˜€ ๋ณด์ž.

๊ทธ๋ฆฌ๊ณ  ์ด์ „๊ณผ ๊ฐ™์ด ๋‹ค๋ฅธ ํƒ€์ž…์œผ๋กœ ์บ์ŠคํŒ… ํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์•Œ์•„๋ณผ ์˜ˆ์ •์ด๋‹ค.

Codable ํŒŒํŠธ๋งŒ 3ํŽธ์œผ๋กœ ๋‚˜๋ˆ ์•ผ ํ•  ๋“ฏ ํ•˜๋‹ค.

์ด๊ฒƒ๊นŒ์ง€ ํ•˜๊ณ  ๋‚˜์„œ์•ผ ๋น„๋กœ์„œ ์‹ค๋ฌด์— ์จ๋„ ๋˜๊ฒ ๋‹ค๋Š” ํ™•์‹ ์ด ๋“ค์—ˆ๋‹ค.



์ฒซ๋ฒˆ์งธ JSON ํ˜•ํƒœ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค

{
    "usrNm" : "KJS"
}


๊ทธ๋ฆฌ๊ณ  Class ํ˜•ํƒœ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค

class UserClass: Decodable {
    var usrNm: String
    var usrAddress: String
}


์—ฌ๊ธฐ์„œ ์ฃผ๋ชฉํ•ด์•ผํ•  ์ ์€ ํด๋ž˜์Šค์—๋Š” usrAddress๋Š” ์กด์žฌํ•˜์ง€๋งŒ JSON์—์„  ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š”์ ์ด๋‹ค.


๋‹ค์‹œ ๋งํ•ด, JSON์— ์—†์„ ๊ฒฝ์šฐ ๊ธฐ๋ณธ๊ฐ’ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ณ  ์‹ถ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.



Wrapper ์ƒ์„ฑ

@propertyWrapper
struct JsonStringWrapper: Decodable {
    let wrappedValue: String
}


๋žฉํผ๊ฐ€ Decodable ํ”„๋กœํ† ์ฝœ์„ ๋ฐ›๊ณ  ์žˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  init decode ๋ ๋•Œ, ์ •์˜๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

extension JsonStringWrapper {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        self.wrappedValue = try container.decode(String.self)
    }
}


์—ฌ๊ธฐ๊นŒ์ง„ ์ด์ „ํŽธ๊นŒ์ง€ ๊ฐ™๋‹ค.

๋ฌธ์ œ๋Š” ์—†๋Š” ๊ธฐ๋ณธ ๊ฐ’์— ๋Œ€ํ•ด ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•˜๋‹ค.


์šฐ์„  ์œ„์˜ ๋žฉํผ์— init()์„ ์ถ”๊ฐ€ํ•ด์ฃผ์ž

@propertyWrapper
struct JsonStringWrapper: Decodable {
    let wrappedValue: String
    
    init() {
        wrappedValue = ""
    }
}

๊ทธ๋Ÿผ ๋‹จ์ˆœ ์ดˆ๊ธฐํ™”์‹œ ๋นˆ๊ฐ’์„ ๊ฐ€์ง€๊ฒŒ ๋œ๋‹ค.


๊ทธ๋ฆฌ๊ณ  ๋‹ค์Œ์ด ํ•ต์‹ฌ์ด๋‹ค.

extension KeyedDecodingContainer {
    func decode(_ type: JsonStringWrapper.Type, forKey key: Key) throws -> JsonStringWrapper {
        try decodeIfPresent(type, forKey: key) ?? .init()
    }
}

key๊ฐ€ ์กด์žฌํ•˜๋ฉด extension JsonStringWrapper ๋ถ€๋ถ„์— decode๋ฅผ ํƒ€๊ฒŒ๋˜๊ณ 

ํ•ด๋‹น ํ‚ค๊ฐ€ ์—†๋‹ค๋ฉด decodeIfPresent๋Š” nil์„ ๋ฑ‰๊ธฐ ๋•Œ๋ฌธ์— .init() ์ด ๋œ๋‹ค.

์ด๊ฑธ ๋จผ์ € ํ–ˆ์–ด์•ผํ–ˆ๋Š”๋ฐ, ์ „ํŽธ ๋‚œ์ด๋„๊ฐ€ ๋” ๋†’์•˜๋˜๊ฑฐ ๊ฐ™๋„ค? ์•„๋‹Œ๊ฐ€ ๋ชจ๋ฅด๊ฒ ๋‹ค. ์ € extension KeyedDecodingContainer์€ ๋‚˜๋„ ๋„ˆ๋ฌด ์ƒ์†Œํ•ด์„œ ์ด๊ฒŒ ๋” ์–ด๋ ค์šด๊ฑฐ ๊ฐ™๊ธฐ๋„ํ•˜๊ณ โ€ฆ


์œ„์˜ ๋ง์„ ๊ฑฐ์˜ ๊ฐ™์ง€๋งŒ ์กฐ๊ธˆ ๋‹ค๋ฅด๊ฒŒ ํ•ด๋ณด์ž๋ฉด

key๋Š” ์œ„ ์˜ˆ์‹œ JSON ๊ฐ’ ์•ˆ์— usrNm์ด๋ผ๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•˜๊ณ ,

์ด๊ฒŒ ์กด์žฌํ•˜๋ฉด ๊ธฐ์กด init(from decoder:)๋ฅผ ํƒœ์›Œ์„œ ์ดˆ๊ธฐํ™”๋ฅผ ์‹œํ‚ค๋Š” ๊ฒƒ์ด๊ณ 

์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด ๋ฏธ๋ฆฌ ์ •์˜ํ•œ init()์„ ํƒ€๊ฒŒ ๋˜๋Š” ๊ฒƒ์ด๋‹ค.


๊ฑฐ์˜ ๊ฐ™์€ ๋ง์ด์ง€๋งŒ ๋ฐ˜๋ณต์€ ์ค‘์š”ํ•˜๋‹ˆ๊น..!

๊ฒฐ๋ก ์€ ์ €๋ ‡๊ฒŒํ•˜๋ฉด ์„œ๋ฒ„์—์„œ JSON์— ๊ฐ’์ด ์•ˆ๋‚ด๋ ค์™€๋„ Decodable์„ ์ด์šฉํ•ด ๋ฏธ๋ฆฌ ์ •ํ•œ ์ดˆ๊ธฐ๊ฐ’์œผ๋กœ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค๋Š”๊ฒƒ!

์™œ ์ด๊ฒƒ์„ ํ–ˆ๋Š”์ง€๊ฐ€ ์ค‘์š”ํ•˜๋‹ค.


์ด๊ฒƒ์„ ์‹œ์ž‘ํ•œ ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ์ •๋ฆฌํ•ด๋ณด์ž๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.



1.Codable์„ ์ด์šฉํ•˜์—ฌ

์„œ๋ฒ„์—์„œ JSON ๊ฒฐ๊ณผ ๊ฐ’์„ ๊ฐ์ฒดํ™” ํ•  ์ˆ˜ ์žˆ๋‹ค

์•„์‰ฌ์šด์ 

JSON ๊ฒฐ๊ณผ์— ์›ํ•˜๋Š” ๊ฐ’์ด ์—†์„๋•Œ, 0๊ณผ ๊ฐ™์€ ์ดˆ๊ธฐ๊ฐ’์„ ์ฃผ๊ณ  ์‹ถ๋‹ค๊ฑฐ๋‚˜ ์ž๋ฃŒํ˜•์„ ๋ณ€๊ฒฝํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด

init(from decoder: Decoder) ๋ถ€๋ถ„์— ๋ชจ๋“  ํ”„๋กœํผํ‹ฐ์— ๋Œ€ํ•ด ๋‹ค ์„ค์ •์„ ํ•ด์ค˜์•ผ ํ•œ๋‹ค.

init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()
    // ๋ชจ๋“  ํ”„๋กœํผํ‹ฐ ์„ ์–ธํ•ด์ฃผ๊ธฐ
    self.propertyA = try container.decode(String.self)
    self.propertyB = try container.decode(Bool.self)
    self.propertyC = try container.decode(Int.self)
    self.propertyD = try container.decode(String.self)
    ... 
}


๋ฐ”๋ผ๋Š”์ 

ํ”„๋กœํผํ‹ฐ ๊ฐฏ์ˆ˜ ๋งŒํผ initํŒŒํŠธ์— ์ญ‰ ๋‚˜์—ดํ•˜๋Š” ๊ฒƒ์€ ๊ฐœ์„ ์˜ ํ•„์š”์„ฑ์„ ๋Š๋‚Œ



2. Property Wrapper๋ฅผ ์ด์šฉํ•˜๋ฉด

init(from decoder: Decoder)๋ฅผ ์•ˆํ•ด๋„ ๋œ๋‹ค.

์žฅ์ 

@propertyWrapper
struct StringBoolConverter {
    let wrappedValue: Bool
}

extension StringBoolConverter: Codable {
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let stringBool = try? container.decode(String.self)
        wrappedValue = stringBool == "Y"
    }
}

์œ„์™€ ๊ฐ™์€ ๋žฉํผ๋ฅผ ๋งŒ๋“ค์–ด ๋‘”๋‹ค๋ฉด


{
  "isHidden" : "Y"
}

์œ„์™€ ๊ฐ™์€ JSON ๊ฒฐ๊ณผ๋Š”


struct Model: Codable {
    @StringBoolConverter var isHidden: Bool
}

์œ„์™€ ๊ฐ™์ด ํ•ด์ฃผ๋ฉด ๊น”๋”ํ•˜๊ฒŒ init(from decoder:)ํŒŒํŠธ ์—†์ด ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.


์•„์‰ฌ์šด์ 

JSON ๊ฒฐ๊ณผ ๋‚ด์— isHidden์ด๋ผ๋Š” ๊ฐ’์ด ์—†๋‹ค๋ฉด init์‹œ throw๋ฅผ ํ•˜๊ฒŒ ๋˜์–ด ๊ฐ์ฒด ์ƒ์„ฑ์ด ๋˜์ง€ ์•Š๋Š”๋‹ค.


๋ฐ”๋ผ๋Š”์ 

ํ”„๋กœํผํ‹ฐ๋Š” ๋งŒ๋“ค์–ด๋’€์ง€๋งŒ JSON๊ฒฐ๊ณผ ๋‚ด์— ํ•ด๋‹น ํ‚ค๊ฐ€ ๋‚ด๋ ค์˜ค์ง€ ์•Š์„ ๊ฒฝ์šฐ ๋ฏธ๋ฆฌ ์ง€์ •ํ•œ default๊ฐ’์œผ๋กœ ์ดˆ๊ธฐํ™” ๋˜๊ธธ ๋ฐ”๋žŒ.



3. 2์˜ Property Wrapper ๊ฐœ์„ 

์œ„์— ์„ธ๋ฒˆ์ด๋‚˜ ๋งํ–ˆ์ง€๋งŒ ํ•œ๋ฒˆ๋” ๋งํ•˜์ž๋ฉด key๊ฐ€ ์กด์žฌํ•˜๋ฉด ๋ฏธ๋ฆฌ ์ •์˜ํ•œ init์„ ํƒœ์šฐ๊ฒŒ ํ•˜๊ณ 

ํ‚ค๊ฐ€ ์—†๋‹ค๋ฉด ๋ฏธ๋ฆฌ ์ •ํ•œ ์ดˆ๊ธฐ๊ฐ’์œผ๋กœ ์ดˆ๊ธฐํ™”๋˜๊ฒŒ ํ•˜์ž

@propertyWrapper
struct JsonStringWrapper: Decodable {
    let wrappedValue: String
    
    init() {
        wrappedValue = ""
    }
}

extension KeyedDecodingContainer {
    func decode(_ type: JsonStringWrapper.Type, forKey key: Key) throws -> JsonStringWrapper {
        try decodeIfPresent(type, forKey: key) ?? .init()
    }
}


์žฅ์ 

JSON์— ์ •์˜ํ•œ ํ‚ค๊ฐ€ ์—†์–ด๋„ init()์— ์ •์˜ํ•œ๋Œ€๋กœ ๋žฉํผ๊ฐ’์€ ""์œผ๋กœ ๋“ค์–ด๊ฐ€๊ฒŒ ๋œ๋‹ค.


์•„์‰ฌ์šด์ 

๋งˆ์ง€๋ง‰ ์ฝ”๋“œ๋งŒ ๋ณด์ž

extension KeyedDecodingContainer {
    func decode(_ type: JsonStringWrapper.Type, forKey key: Key) throws -> JsonStringWrapper {
        try decodeIfPresent(type, forKey: key) ?? .init()
    }
}

์ด๋ถ€๋ถ„์€ type์— ์ด๋ฒˆ์— ๋งŒ๋“  JsonStringWrapper์— ๋Œ€ํ•ด์„œ๋งŒ ๋ฐ˜์‘์„ ํ•˜๋Š” ๋ฉ”์†Œ๋“œ์ด๋‹ค.


๋‹ค์‹œ ๋งํ•ด,

JSON ๊ฒฐ๊ณผ ๊ฐ’๋“ค ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ Int, Double, Float, Bool ๋“ฑ์„ ๋žฉํผ๋กœ ๋งŒ๋“ค๊ฒŒ ๋œ๋‹ค๋ฉด

ํ•ด๋‹น ๋ฉ”์†Œ๋“œ๋Š” ๋™์ผํ•˜๊ฒŒ ์ฆ๊ฐ€๋˜๊ฒŒ ๋œ๋‹ค.

๊ทธ๋Ÿผ์— ๋”ฐ๋ผ ์ด๊ฒƒ ๋˜ํ•œ ์•„์‰ฝ๋‹ค๊ณ  ๋Š๊ปด์ง€๋ฉฐ ๊ฐœ์„ ์˜ ํ•„์š”์„ฑ์„ ๋Š๋ผ๊ฒŒ ๋˜์—ˆ๋‹ค.


๊ทธ๋ž˜์„œ ์ด ์•„์‰ฌ์›€์„ ๋˜ ๋‹ฌ๋ž˜๊ธฐ ์œ„ํ•ด ๋‹ค์ŒํŽธ์—” Generic์„ ์ด์šฉํ•˜์—ฌ

ํ•ด๋‹น ๋ถ€๋ถ„์„ ์ค„์ด๋Š” ๊ฒƒ์— ์ดˆ์ ์„ ๋งž์ถฐ ๊ธ€์„ ์จ๋ณด๊ณ ์ž ํ•œ๋‹ค.


๋.