Jiseob Kim

iOS Developer

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

20 Jun 2021 » Swift

์ดํŽธ์˜ ๋‹จ์ ์ด ์กด์žฌํ•จ์œผ๋กœ ๋๊นŒ์ง€ ์ฝ๊ธฐ์™€ ๋‹ค์ŒํŽธ์€ ํ•„์ˆ˜๋กœ ์ฝ๋Š” ๊ฒƒ์„ ๊ถŒ์žฅ!


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


์ง€๋‚œ๋ฒˆ์˜ ๊ธ€์˜ ์ฃผ์ œ๊ฐ€ Property Wrapper ์˜ ๊ธฐ๋ณธ๊ณผ UserDefault ์— ์ ์šฉํ•˜๋Š” ๊ธ€์ด์—ˆ๋‹ค๋ฉด,

์ด๋ฒˆํŽธ์€ Codable์— ์ ์šฉ์„ ํ•ด๋ณด๋Š” ๊ฒƒ์ด ์ด๋ฒˆ ๊ธ€์˜ ์ฃผ์ œ!

์ •ํ™•ํžˆ๋Š” Decodable์— ์ ์šฉ! ํ•˜์ง€๋งŒ Codable์ด ๋” ์ต์ˆ™ํ• ํ…Œ๋‹ˆ Codable์ด๋ผ ํ•˜๊ธฐ.



์ด ํฌ์ŠคํŒ…์„ ํ•˜๊ฒŒ๋œ ์ด์œ 

๋‹ค์Œ๊ณผ ๊ฐ™์€ JSON์ด ์žˆ๋‹ค๊ณ  ํ•ด๋ณด์ž

{
  "isHidden" : "Y"
}


ํ•ด๋‹น ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด ์•„์‰ฌ์›€์ด ์žˆ๋‹ค.

isHidden ์€ Bool ํƒ€์ž…์ด ์•„๋‹ˆ์—ฌ์„œ ์กฐ๊ฑด๋ฌธ(if or switch) ๋“ฑ์—์„œ String ๋น„๊ต๊ฐ€ ๋“ค์–ด๊ฐ„๋‹ค

// ์ „์ œ์กฐ๊ฑด: Model์ด๋ž€ ๊ตฌ์กฐ์ฒด์— ํŒŒ์‹ฑ ๋˜์—ˆ๋‹ค๋Š” ๊ธฐ์ค€ํ•˜์— ์ž‘์„ฑ
if model.isHidden == "Y" {
    // ์–ด๋–ค ์–ด๋–ค ์ฒ˜๋ฆฌ
}


ํ•˜์ง€๋งŒ, ๊ฐœ์ธ์ ์œผ๋ก  ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋˜๊ธธ ๋ฐ”๋ž€๋‹ค.

if model.isHidden {
    // ์–ด๋–ค ์–ด๋–ค ์ฒ˜๋ฆฌ
}


๊ทธ๋ ‡๊ฒŒ ๋˜๊ธฐ ์œ„ํ•ด์„  isHidden์ด๋ผ๋Š” ๊ฐ’์ด Boolํ˜•ํƒœ์—ฌ์•ผ ํ•œ๋‹ค.

์ด ์ ์€ ์ด์ „์— CodingKey ๊ด€๋ จ ๊ธ€์—์„œ ํ•ด๊ฒฐํ•˜์˜€๋‹ค. ์ฐธ๊ณ 



๊ฐœ์„ ํ•œ ๊ฒƒ์˜ ๋‹จ์ 

์œ„์˜ ๋ฐฉ์‹์€ ์‹ค์ œ ์‚ฌ์šฉํ•˜๋Š” ์ฝ”๋“œ์—์„œ ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ์Šคํƒ€์ผ๋กœ ์ฝ”๋”ฉ์„ ํ•  ์ˆ˜ ์žˆ์–ด์„œ ์ข‹์•˜๋‹ค.

ํ•˜์ง€๋งŒ, ๋‹จ์ ์ด ๋”ฐ๋ผ์™”๋‹ค.

    init(from decoder: Decoder) throws {}


ํ•ด๋‹น ์ฝ”๋“œ๊ฐ€ ๊ฐ™์ด ์กด์žฌํ•ด์•ผํ•œ๋‹ค.


๋‹ค์‹œ๋งํ•ด, ๋‹ค์Œ ์ฝ”๋“œ์˜ ์ฃผ์„1,2์™€ ๊ฐ™์ด ๊ฐ ํ”„๋กœํผํ‹ฐ๋“ค์„ ๋‹ค ์ดˆ๊ธฐํ™” ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

// ์ด์ „๊ธ€์ค‘ ์ผ๋ถ€ ๋ถ€๋ถ„ (https://jiseobkim.github.io/swift/network/2021/05/26/swift-CodingKey-API์™€-๋‹ค๋ฅธ-์ž๋ฃŒํ˜•-์“ฐ๊ธฐ.html)
init(from decoder: Decoder) throws {
    let container = try decoder.container(keyedBy: CodingKeys.self)
    
    // 1. ์‚ฌ์šฉ์ž๋ช… ์ดˆ๊ธฐํ™”
    self.userName = try container.decode(String.self, forKey: .userName)
    // 2. ์ˆจ๊น€์ฒ˜๋ฆฌ ์ดˆ๊ธฐํ™”
    self.isHidden = try container.decode(String.self, forKey: .isHidden) == "Y"
}


์—ฌ๊ธฐ์„œ๋Š” ๊ฐ’์ด 2๊ฐœ๋‹ค. ๋”ฐ๋ผ์„œ ๋ผ์ธ๋„ 2์ค„์ด ๋”ฐ๋ผ์˜จ๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ์—ฌ๊ธฐ์„œ ๋งŒ์•ฝ ์ž๋ฃŒํ˜•์„ ๋ฐ”๊พธ๊ฒŒ ๋œ๋‹ค๋ฉด ์ฝ”๋“œ๊ฐ€ ๊ธธ์–ด์ง€๊ฑฐ๋‚˜ ์‹ฌํ•˜๋ฉด ๋ผ์ธ์ด ๋ช‡์ค„ ์ถ”๊ฐ€๊ฐ€ ๋œ๋‹ค.


์ฃผ์„ 2๋ฒˆ๊ณผ ๊ฐ™์ด ์ž๋ฃŒํ˜• ๋ณ€๊ฒฝ์ด๋‚˜ ํ”„๋กœํผํ‹ฐ๊ฐ€ ๋งŽ๋‹ค๋ฉด

๊ฐ initํŒŒํŠธ๋งˆ๋‹ค ๋ผ์ธ์˜ ์ˆ˜๋Š” ๊ฐ™์ด ์ฆ๊ฐ€ํ•˜๊ฒŒ ๋œ๋‹ค.

์ด๊ฒŒ ์€๊ทผ ๊ท€์ฐฎ๋‹ค.

ํŽธํ•˜์ž๊ณ  ํ•œ๊ฑด๋ฐ, ํŽธํ•˜์ž๊ณ  ์ค€๋น„๊ฐ€ ๋„ˆ๋ฌด ๊ธด ๋Š๋‚Œ์ด๋ž„๊นŒ?


๊ทธ๋ž˜์„œ ๋ฐ˜๋ณต์ ์ธ ์ด ๋ถ€๋ถ„์„ ๊ฐœ์„ ํ•˜๊ณ  ์‹ถ์—ˆ๋˜ ์ฐฐ๋‚˜์— Property Wrapper๋ฅผ ์ ์šฉ ๊ฐ€๋Šฅํ•œ ๊ฒƒ์„ ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค.



Property Wrapper์™€ Codable

UserDefault์—์„œ๋„ ๋ณด์•˜๋“ฏ์ด Property Wrapper๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ฐ˜๋ณต์ ์ธ ํ–‰์œ„๋ฅผ ์ œ๊ฑฐํ•˜์˜€๋‹ค. ์—ฌ๊ธฐ์„œ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€๋‹ค.

์ดˆ๊ธฐํ™” ๊ณผ์ •์—์„œ ๋ฐ˜๋ณต๋˜๋Š” ํ–‰์œ„๋ฅผ ์ œ๊ฑฐํ•˜๋Š” ๊ฒƒ์ด ๋ชฉ์ !


Property Wrapper ์ž‘์„ฑ

@propertyWrapper
struct StringBoolConverter {
    let wrappedValue: Bool
}

์ด๋ฆ„๊ณผ ๊ฐ™์ด Bool ์ด์ง€๋งŒ ๊ฐ’์€ String์ธ ๊ฒƒ์„ ๋ฐ”๊ฟ”์ฃผ๋Š” ๊ทธ๋Ÿฐ ์—ญํ• ์ด๋‹ค.


์ด์ „๊ณผ๋Š” ๋‹ค๋ฅด๊ฒŒ init ๋ถ€๋ถ„ ๋˜ํ•œ ์—†์œผ๋ฉฐ get/set ๋ถ€๋ถ„๋„ ์—†๋‹ค.

  • init์ด ์—†๋Š” ์ด์œ ๋Š” Decodable์— ์˜ํ•ด ์ดˆ๊ธฐํ™”๊ฐ€ ๋  ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
  • get/set์ด ์—†๋Š” ์ด์œ ๋Š” UserDefault์ฒ˜๋Ÿผ ๊ฐ’์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ์—ฐ์‚ฐ์ด ํ•„์š” ์—†๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.


์ด๊ฑฐํ•˜๋‹ค๊ฐ€ ์ €๋ฒˆ๊ธ€์˜ Property Wrapper๋ฅผ โ€œํ”„๋กœํผํ‹ฐ๋ฅผ ์ดˆ๊ธฐํ™” ํ• ๋•Œ, ์—ฐ์‚ฐ ํ”„๋กœํผํ‹ฐ(getter/setter) ๊ฐœ๋…์„ ์ถ”๊ฐ€ ์ ์šฉํ•˜๋Š” ๊ฒƒ (100% ๊ฐœ์ธ ์ƒ๊ฐ)โ€ ๋ผ๊ณ  ์š”์•ฝํ–ˆ๋Š”๋ฐ, ์ด ๋ง์— ๋Œ€ํ•ด ์กฐ๊ธˆ ๋” ๊ณ ๋ฏผ ํ•ด๋ด์•ผ๊ฒ ๋‹ค. โ€œ์ดˆ๊ธฐํ™”๋ฅผ ์ปค์Šคํ…€ ํ•  ์ˆ˜ ์žˆ๋‹ค.โ€ ๋ผ๋Š” ๋ง์ด ๋” ์–ด์šธ๋ฆด๋ ค๋‚˜โ€ฆ?


๊ทธ๋Ÿผ ์ด์ œ Extension์„ ํ•ด๋ณด์ž

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


์ด ๋ถ€๋ถ„์€ ์–ด๋ ต์ง€ ์•Š์„ ๊ฒƒ์ด๋‹ค.

๋‚ด ๋ธ”๋กœ๊ทธ์—์„œ ๋‚˜์˜ค์ง€ ์•Š์€ singleValueContainer()์˜ ๊ฒฝ์šฐ ํ”„๋กœํผํ‹ฐ ๋„ค์ž„๊ณผ ๋™์ผํ•œ ๊ฒƒ์„ ์ฐพ์•„์„œ decode ํ•ด์ฃผ๋Š” ์—ญํ• ์ธ๋“ฏํ•˜๋‹ค.

CodingKey๋ฅผ ๋”ฐ๋กœ ์ž‘์„ฑํ•˜์ง€ ์•Š์œผ๋ฉด ๊ธฐ๋ณธ์ ์œผ๋กœ ์ด๋ฃจ์–ด์ง€๋Š” ๋ฐฉ์‹์œผ๋กœ ์˜ˆ์ƒ๋œ๋‹ค.


์“ฐ๋Š” ๋ฐฉ์‹์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

๋ชจ๋ธ์„ ๋ณด์ž

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


์•„์ฃผ ๊ฐ„๋‹จํ•˜๋‹ค!

@StringBoolConverter๋งŒ ๋ถ™์—ฌ์ฃผ๋ฉด ์ด isHidden์ด๋ผ๋Š” ๊ฐ’์€ String์œผ๋กœ ์˜จ Y ๋˜๋Š” N ๊ฐ’์— ์˜ํ•ด Bool๋กœ ๋ณ€ํ™˜ ๋˜์–ด ์ดˆ๊ธฐํ™”๊ฐ€ ๋œ๋‹ค.


์‹ค์ œ๋กœ ์“ฐ๊ณ , ์ถœ๋ ฅ๋œ ๊ฒฐ๊ณผ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

์ž˜๋‚˜์™”๋‹ค!



์ „์ฒด ์ฝ”๋“œ๋ฅผ ๋น„๊ตํ•ด๋ณด์ž.


Property Wrapper ์‚ฌ์šฉ ์ „

struct Model: Codable {
    let isHidden: Bool
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        
        self.isHidden = try container.decode(String.self) == "Y"
    }
}


Property Wrapper ์‚ฌ์šฉ ํ›„

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

init๋ถ€๋ถ„์ด ์ œ๊ฑฐ๋จ์— ๋”ฐ๋ผ ์•„์ฃผ ์‹ฌํ”Œํ•ด์กŒ๋‹ค!

์œ„์— ๋งํ•œ๋ฐ”์™€ ๊ฐ™์ด Property Wrapper๋กœ ์ธํ•ด ๋ฐ˜๋ณต์ ์ธ ์ž‘์—…์ด ์ œ๊ฑฐ๋˜์—ˆ๋‹ค.



๋‹จ์ 

๊ต‰์žฅํžˆ ์ข‹์Œ์—๋„ ๋‹จ์ ์ด ์กด์žฌํ•œ๋‹ค.

๊ทธ๊ฒƒ์€ nil์ฒ˜๋ฆฌ๊ฐ€ ์œ„์™€ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ๋Š” ์•ˆ๋œ๋‹ค๋Š” ์ ์ด๋‹ค.

์œ„์˜ ์ฝ”๋“œ ์˜ˆ์‹œ์˜ ๊ฒฝ์šฐ json์— isHidden์ด๋ผ๋Š” ๊ฐ’์ด ์กด์žฌํ•œ๋‹ค.

ํ•˜์ง€๋งŒ, isHidden์ด๋ผ๋Š” ๊ฐ’์ด ์—†์„ ๊ฒฝ์šฐ, ๋ชจ๋ธ์— ํ•ด๋‹น ๊ฐ’์„ ์˜ต์…”๋„์ฒ˜๋ฆฌ ํ•ด์ฃผ๋”๋ผ๋„ ์ด ์ฝ”๋“œ๋Š” throwํ•˜๊ฒŒ ๋œ๋‹ค.


๋‹ค์‹œ ๋งํ•ด ์ดˆ๊ธฐํ™”๊ฐ€ ์•ˆ๋œ๋‹ค!


์‹ค์ œ๋กœ ์„œ๋ฒ„์—์„œ๋„ Request์— ๋”ฐ๋ผ Response๊ฐ€ ๋‹ฌ๋ผ์งˆ ๊ฐ€๋Šฅ์„ฑ์€ ์กด์žฌํ•œ๋‹ค.

๊ทธ๋ ‡๊ธฐ์— ์ด ๋‹จ์ ์€ ์น˜๋ช…์ ์ด๋ผ ์ƒ๊ฐ๋œ๋‹ค.


ํ•˜์ง€๋งŒ, ์ด ๋‹จ์  ๋˜ํ•œ ์ปค๋ฒ„๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.

๊ทธ ํฌ์ŠคํŒ…์€ ๋‹ค์ŒํŽธ์—~~