[swift] JSON데이터 받아오기
1️⃣ 목표
2️⃣ 기본 구현
(1) 데이터 모델(DataModel.swift)
- 먼저 받아올 데이터(JSON)의 구조체를 잡아줍니다.
- 각각의 변수명은 받아올 데이터의
key 값과 정확히일치 해야 정상적으로 불러올 수 있습니다.
struct DataModel: Codable {
let resultCount: Int
let result: [Result]
}
struct Result: Codable {
let username: String
let age: Int
let gender: String
}
- 혹은 다음과 같이
CodingKey
를 이용하여 자신만의 변수명으로 설정할 수 있습니다. - 주의할 점은 반드시 enum의 명을 “CodingKeys”로 해야하며, 변경이 없더라도 해당 구조체의 변수를 case로 모두 등록해 주어야 합니다.
struct DataModel: Codable {
let cnt: Int
let result: [Result]
enum CodingKeys: CodingKey, String {
case cnt = "resultCount"
case result // 바꾸지 않는 변수도 선언
}
}
struct Result: Codable {
let username: String
let age: Int
let gender: String
}
(2) 데이터 얻어오기(Data(contentsOf:) 이용)
- 다음과 같이 간단하게 Data(contentsOf:)를 이용하여 데이터를 불러올 수있습니다.
class MainVC: UIViewController, UITableViewDataSource {
private var myData: DataModel?
@IBOutlet weak var mainTableView: UITableView!
override func viewDidLoad() {
/* 생략 */
self.getDataSimple()
}
private func getDataSimple() {
guard let url = URL(string: "http://localhost:8080") else { return }
do {
let data = try Data(contentsOf: url)
self.myData = try JSONDecoder().decode(DataModel.self, from: data)
self.mainTableView.reloadData()
} catch {
print(error)
}
}
/* 테이블 뷰 데이터소스설정 메서드 생략 */
}
(2 - 2) 멀티스레드를 이용해서 처리하기(Data(contentsOf:) 이용)
- 그렇다면 위의 문제를 어떻게 해결해볼 수 있을까요?
- 한가지 방법으로 DispatchQueue.global().async를 이용해서 처리하는 것 입니다.
- 결과적으로 아래와 같이 작성하면
런타임 오류 가 발생합니다. (이유는 아래 설명)
class MainVC: UIViewController, UITableViewDataSource {
/* 생략 */
private func getDataSimple() {
guard let url = URL(string: "http://localhost:8080") else { return }
DispatchQueue.global().async {
do {
let data = try Data(contentsOf: url)
self.myData = try JSONDecoder().decode(DataModel.self, from: data)
self.mainTableView.reloadData()
} catch {
print(error)
}
}
}
/* 테이블 뷰 데이터소스설정 메서드 생략 */
}
- 위의 문제는 mainTableView를 리로드를 처리하는 부분을 다음과 같이 메인쓰레드에서 처리하도록 해주면 해결 됩니다.
DispatchQueue.main.async {
self.mainTableView.reloadData()
}
- 한가지 의문이 드는 것이
DispatchQueue.global().async
에서async
는 asynchronous(비동기)의 약자일 것입니다. 하지만 위의 에러를 봤듯이 다른 스레드에서 처리해서 에러가 발생한 것을 알 수 있습니다. - 비동기 처리와 멀티쓰레드 처리는 비슷한 것 같지만 다른개념입니다. OKKY 커뮤니티의 어느분께서 이것에 대해서 아래와 같이 쉽게 설명해 주셨습니다.
- 그렇다면 멀티스레드 - 비동기가 동시에 적용되는 것인가 의문이 들었습니다.
- 먼저 다음과 같은 간단한 예시를 이용했습니다.
DispatchQueue.main.async {
sleep(2)
print("first")
}
DispatchQueue.main.async {
print("second")
}
first
second
- 동일한 메인쓰레드에서 처리하도록 해봤습니다. 기대와 다르게
동기적 으로 처리 되었습니다. - 그러면 다음과 같이 메이쓰레드가 아닌 다른 동일한 쓰레드에서 처리한다면 어떻게 될까요.
let kirQueue = DispatchQueue(label: "kirkim")
kirQueue.async {
sleep(2)
print("first")
}
kirQueue.async {
print("second")
}
first
second
- 이것도
동기적 으로 처리 되었습니다. - 위의 정보들을 종합해 봤을 때 멀티쓰레드처리 방식인 것 같습니다. 혹은 자바스크립트에서의 비동기와는 다른개념이거나 아니면 위의 예시들이 잘못됐을 수도 있을 것 같습니다.
- 이 부분에 대해서는 좀 더 공부해봐야할 것 같습니다.
- 아무튼
DispatchQueue
의 사용법에 대해 좀 더 알고 싶다면 GCD - Dispatch Queue사용법(1) - Zedd0202포스트를 참고하면 좋을 것 같습니다.
(3) 데이터 얻어오기(URLSession을 이용)
- [iOS] Image를 URLSession / Kingfisher를 통해 가져오기 - 나무’s블로그글을 참고해보면 이미지나 큰 데이터를 받아올 떄는 위에서 사용해본
Data()
메서드를 이용하지 말라고 합니다. Apple documentation을 봐도 간단한 데이터를 처리한다고 언급하고 있습니다.
- NSURLSession - Apple documentation에서도 길게 설명하는데 URLSession을 이용해서 구현하는 방법을 익히는 것이 좋을 것같습니다. (URLSession이 왜 큰 데이터를 처리하는데 유리한지에 대해 꼭 다시 공부해봐야 겠다.)
- 일단 다른 블로그글들을 참고하여 다음과 같이 URLSession을 활용하여 데이터를 받아오는 코드를 구현했습니다.
class MainVC: UIViewController, UITableViewDataSource {
/* 생략 */
private func getDataUseSession() {
let session = URLSession.shared
let components = URLComponents(string: "http://localhost:8080")
guard let url = components?.url else { return }
var request = URLRequest(url: url)
request.httpMethod = "GET"
session.dataTask(with: request) { data, res, error in
if let hasData = data {
do {
self.myData = try JSONDecoder().decode(DataModel.self, from: hasData)
DispatchQueue.main.async { // UI에 관여하는 작업은 메인쓰레드에서 처리
self.mainTableView.reloadData()
}
} catch {
print(error)
}
}
}.resume()
session.finishTasksAndInvalidate()
}
/* 테이블 뷰 데이터소스설정 메서드 생략 */
}
👉🏻👉🏻👉🏻 위의 코드를 바탕으로 만든 포스트 [swift] 비동기데이터를 처리하는 클래스(or 함수) 만들기(새창) 보러가기