Codable Protocol์„ ์ด์šฉํ•ด์„œ ๋ณต์žกํ•œ JSON Parsing ํ•˜๊ธฐ
iOS ๐Ÿ”ฅ/Swift

Codable Protocol์„ ์ด์šฉํ•ด์„œ ๋ณต์žกํ•œ JSON Parsing ํ•˜๊ธฐ

์ œ๊ฐ€ ์˜ˆ์ „์— ์ง„ํ–‰ํ–ˆ๋˜ ํ•œ์–‘๋Œ€ํ•™๊ต iOS ์•ฑ์—์„œ, ๋ฉ”๋‰ด ํ™”๋ฉด ๊ทธ๋ฆด ๋•Œ URLSession์„ ํ†ตํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ JSON์„ Parsing ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.

Swift4๋ถ€ํ„ฐ Codable Protocol์ด ๋„์ž…๋ผ์„œ, JSON์„ ๊ต‰์žฅํžˆ ํŽธ๋ฆฌํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์šฐ์„  ์ œ๊ฐ€ ์„œ๋ฒ„์™€ ํ†ต์‹  ํ•  JSON ๊ทœ๊ฒฉ์€ ๋‹ค์Œ์ฒ˜๋Ÿผ ๋ณต์žกํ•˜๊ฒŒ ๋˜์–ด ์žˆ์–ด์š”.

{
    "result": {
        "totalCount": 9,
        "list": [
            {
                "ACT_URL": null,
                "HOCHUL_GB": "L",
                "ICON_NM": "ic_hanyang_sogae_l",
                "MENU_NM": "ํ•œ์–‘์†Œ๊ฐœ",
                "MENU_SEQ": 1,
                "MENU_ID": "911",
                "MENU_ID2": "M319378"
            },
            {
                "MENU_SEQ": 2,
                "MENU_ID2": "M319362",
                "ACT_URL": null,
                "HOCHUL_GB": "L",
                "ICON_NM": "ic_seoul_campus_l",
                "MENU_ID": "912",
                "MENU_NM": "์„œ์šธ์บ ํผ์Šค"
            },
            {
                "MENU_ID2": "M319363",
                "MENU_ID": "913",
                "MENU_SEQ": 3,
                "ICON_NM": "ic_erica_campus_l",
                "HOCHUL_GB": "L",
                "ACT_URL": null,
                "MENU_NM": "ERICA์บ ํผ์Šค"
            },
            {
                "ACT_URL": "https://go.hanyang.ac.kr/new/mobile/main.html",
                "MENU_ID2": null,
                "MENU_SEQ": 4,
                "ICON_NM": "ic_iphak_anne_l",
                "HOCHUL_GB": "U",
                "MENU_ID": "914",
                "MENU_NM": "์ž…ํ•™์•ˆ๋‚ด"
            },
            {
                "MENU_SEQ": 5,
                "ICON_NM": "ic_brain_hanyang_l",
                "ACT_URL": null,
                "MENU_ID2": "M008388",
                "MENU_NM": "๋ธŒ๋ ˆ์ธํ•œ์–‘",
                "HOCHUL_GB": "L",
                "MENU_ID": "915"
            },
            {
                "MENU_NM": "๋ฐœ์ „๊ธฐ๊ธˆ",
                "MENU_SEQ": 6,
                "MENU_ID": "916",
                "MENU_ID2": "M008301",
                "ACT_URL": null,
                "ICON_NM": "ic_baljeon_gigeum_l",
                "HOCHUL_GB": "L"
            },
            {
                "HOCHUL_GB": "U",
                "MENU_SEQ": 7,
                "MENU_ID": "917",
                "ICON_NM": "ic_parking_l",
                "MENU_ID2": null,
                "ACT_URL": "https://parking.hanyang.ac.kr/",
                "MENU_NM": "์ฃผ์ฐจ๊ด€๋ฆฌ"
            },
            {
                "ACT_URL": "https://portal.hanyang.ac.kr/applink/lib.html",
                "MENU_ID2": null,
                "MENU_SEQ": 8,
                "MENU_ID": "918",
                "HOCHUL_GB": "A",
                "ICON_NM": "ic_doseogwan_l",
                "MENU_NM": "๋„์„œ๊ด€"
            },
            {
                "HOCHUL_GB": "U",
                "MENU_ID": "919",
                "ICON_NM": "ic_haksik_l",
                "MENU_ID2": null,
                "MENU_NM": "ํ•™๊ต์‹๋‹จ",
                "MENU_SEQ": 9,
                "ACT_URL": "https://www.hanyang.ac.kr/web/www/re1"
            }
        ]
    },
    "nowDate": {
        "dd": "15",
        "mm": "04",
        "yoil": "5",
        "yyyy": "2021"
    },
    "resultMessage": {
        "service": "/COMM/A202100004",
        "code": 200,
        "msg": ""
    }
}

์šฐ์„  ๊ฐ€์žฅ ์ƒ๋‹จ์— ํ‚ค ๊ฐ’์ด "result", "resultMessage", "nowDate"

์ด ์„ธ๊ฐ€์ง€๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ํฌํ•จํ•˜๋Š” Root๋ฅผ ๋งŒ๋“ค์–ด์ฃผ๊ณ , ๊ทธ ์•„๋ž˜ ๋…€์„๋“ค์„ ํ•˜๋‚˜์”ฉ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด 4๊ฐ€์ง€ ๋ชจ๋ธ์ด ํ•„์š”ํ•ด์š”. 

struct Root<T: Codable>: Codable {
    var result: Result<T>?
    var nowDate: [String:String]?
    var resultMessage: ResultMessage?
}

struct Result<T: Codable>: Codable{
    var list: [T]?
    var totalCount: Int?
}

struct ResultMessage: Codable {
    let code:Int?
    let msg:String?
    let service: String?
}

struct MenuItem: Codable {
    let MENU_NM: String?
    let ICON_NM: String?
    let ACT_URL: String?
    let HOCHUL_GB: String?
    let MENU_ID: String?
    let MENU_ID2: String?
    let MENU_SEQ: Int?
    let DEL_POSSIBLE_YN: String?
}

์ด ๊ฒฝ์šฐ์—๋Š” Generic์„ ์‚ฌ์šฉํ•˜๋ฉด, ํŽธํ•˜๊ฒŒ ํŒŒ์‹ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ๊ตฌ์กฐ์ฒด๋ฅผ ๋งŒ๋“ค์—ˆ์œผ๋ฉด, ํŒŒ์‹ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์€
URLSession์œผ๋กœ request๋ฅผ ๋‚ ๋ฆฌ๊ณ , JSONDecoder๋ฅผ ์ด์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
API ์š”์ฒญ์€ ์ฃผ๋กœ App Lifecycle์—์„œ viewWillAppear์—์„œ ์ฃผ๋กœ ํ•ฉ๋‹ˆ๋‹ค. 

     override func viewWillAppear(_ animated: Bool) {
        if loadingIndicator.isAnimating {
            guard let url = URL(string: "๋น„๊ณต๊ฐœURL") else { return }
            var req = URLRequest(url: url)
            req.httpMethod = "GET"
            req.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
            let task = URLSession.shared.dataTask(with: req) { data, response, error in
                let root = try! JSONDecoder().decode(Root<MenuItem>.self, from: data!)
                self.menuItems = root.result!.list!
            }
            task.resume()
        }
    }

๋กœ๋”ฉ indicator๊ฐ€ ๋Œ๊ณ ์žˆ์œผ๋ฉด, Request๋ฅผ ๋‚ ๋ฆฝ๋‹ˆ๋‹ค.
๊ทธ๋ฆฌ๊ณ  ์„ฑ๊ณต์ ์œผ๋กœ ํŒŒ์‹ฑ์„ ํ•˜๋ฉด self.menuItems์— list๋ฅผ ์„ค์ •ํ•ด์ค๋‹ˆ๋‹ค.

URLSession์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํ์—์„œ ์‹คํ–‰ ๋˜๊ธฐ ๋•Œ๋ฌธ์— UI๋ฅผ ์ˆ˜์ •ํ•˜๋Š” ๋ถ€๋ถ„์€ ๋ฉ”์ธ ์“ฐ๋ ˆ๋“œ์—์„œ ํ•ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.
๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ฐ’์ด ์„ค์ •๋˜๋ฉด, CollectionView๋ฅผ Reloadํ•ด์ฃผ๊ณ , Loading Indicator๋ฅผ ๋ฉˆ์ถ”๋ฉด ๋ฉ๋‹ˆ๋‹ค.

    var menuItems:[MenuItem] = [] {
        didSet {
            DispatchQueue.main.async {
                self.menuCollectionView.reloadData()
                self.loadingIndicator.stopAnimating()
            }
        }
    }

์ดํ›„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ํ™”๋ฉด์ด ๊ฐฑ์‹ ๋จ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค ใ…Žใ…Ž