[swift] JSON데이터 받아오기


1️⃣ 목표

  • 이번 목표는 특정 Url을 통해 데이터를 가져오는 방법에 대해 알아볼 예정입니다.
  • 정상적으로 데이터를 가져오는데 성공하면 오른쪽과 같이 출력 될 것 입니다.
  • finished version
  • Node.js를 이용해서 간단한 백엔드 서버를 만들었습니다.
  • http://localhost:8080get요청을 하면 10개의 유저정보를 얻어 올 수 있도록 구현했습니다.
  • temp backend

    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)
        }
      }
      /* 테이블 뷰 데이터소스설정 메서드 생략 */
    }
    
  • 유저정보가 10개이기 때문에 문제없이 돌아가는 것 같습니다.
  • 그래서 이번엔 유저 데이터를 1000000개로 늘려서 다시 한번 실행해 봤습니다.
  • 오른쪽과 같이 모든 데이터를 불러올때까지 메인 뷰가 열리지 않습니다.
  • 그 이유는 Data(contentsOf:)동기적으로 데이터를 받아오기 때문입니다.
  • sync Data function

    (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)
          }
    	}
      }
      /* 테이블 뷰 데이터소스설정 메서드 생략 */
    }
    
  • 오류가 일어난 이유는 UI의 처리부분을 main쓰레드에서 처리하지 않았기 때문입니다.
  • 위의 코드에서 mainTableView를 리로드하는 과정은 UI에 영향을 주는 부분입니다.
  • thread error


    • 위의 문제는 mainTableView를 리로드를 처리하는 부분을 다음과 같이 메인쓰레드에서 처리하도록 해주면 해결 됩니다.
    DispatchQueue.main.async {
      self.mainTableView.reloadData()
    }
    
    • 한가지 의문이 드는 것이 DispatchQueue.global().async 에서 asyncasynchronous(비동기)의 약자일 것입니다. 하지만 위의 에러를 봤듯이 다른 스레드에서 처리해서 에러가 발생한 것을 알 수 있습니다.
    • 비동기 처리멀티쓰레드 처리는 비슷한 것 같지만 다른개념입니다. OKKY 커뮤니티의 어느분께서 이것에 대해서 아래와 같이 쉽게 설명해 주셨습니다.

    async vs multithread

    • 그렇다면 멀티스레드 - 비동기가 동시에 적용되는 것인가 의문이 들었습니다.
    • 먼저 다음과 같은 간단한 예시를 이용했습니다.
    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을 이용)

    Data 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 함수) 만들기(새창) 보러가기




    © 2021.02. by kirim

    Powered by kkrim