[SwiftUI] Download JSON & @escaping

Junyoung Park·2022년 8월 20일
0

SwiftUI

목록 보기
22/136
post-thumbnail

Download JSON from API in Swift w/ URLSession and escaping closures | Continued Learning #22

Download JSON & @escaping

구현 목표

  • 이스케이핑 클로저를 사용한 비동기적 처리
  • JSON 데이터를 처리한다.

구현 태스크

  1. URLSession을 통해 JSON 데이터를 받아온다.
  2. JSON 데이터를 해당 모델로 디코딩한다.
  3. 비동기적으로 처리한 데이터 모델은 로컬 데이터로 넣는 과정을 백그라운드 스레드에서 처리한다.
  4. UI 업데이트를 메인 스레드에서 처리한다.

핵심 코드

    private func downloadData(from url: URL, completionHandler: @escaping (_ data: Data?) -> ()) {
        URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data, error == nil, let response = response as? HTTPURLResponse, response.statusCode >= 200 && response.statusCode < 300 else {
                print("ERROR DOWNLOADING DATA")
                completionHandler(nil)
                return
            }
            completionHandler(data)
        }.resume()
    }

소스 코드

import SwiftUI

struct PostModel: Identifiable, Codable {
    let userId: Int
    let id: Int
    let title: String
    let body: String
}

class DownloadWithEscapingViewModel: ObservableObject {
    @Published var posts: [PostModel] = []
    
    init() {
        getPosts()
    }
    
    func getPosts() {
        guard let url = getUrl() else { return }
        // BACKGROUND THREAD DOWNLOAD
        
        downloadData(from: url) { data in
            if let data = data {
                guard let posts = try? JSONDecoder().decode([PostModel].self, from: data) else { return }
                DispatchQueue.main.async { [weak self] in
                    self?.posts = posts
                }
            } else {
                print("NO DATA RETURNED")
            }
        }
    }
    
    // GENERIC DATA DOWNLOAD HANDLER
    private func downloadData(from url: URL, completionHandler: @escaping (_ data: Data?) -> ()) {
        URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data, error == nil, let response = response as? HTTPURLResponse, response.statusCode >= 200 && response.statusCode < 300 else {
                print("ERROR DOWNLOADING DATA")
                completionHandler(nil)
                return
            }
            completionHandler(data)
        }.resume()
    }
    
    private func getUrl() -> URL? {
        guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else { return nil }
        return url
    }
}

struct DownloadWithEscapingBootCamp: View {
    @StateObject private var viewModel = DownloadWithEscapingViewModel()
    var body: some View {
        List {
            ForEach(viewModel.posts) { post in
                VStack(alignment: .leading) {
                    Text(post.title)
                        .font(.headline)
                    Text(post.body)
                        .foregroundColor(.gray)
                        .font(.body)
                }
                .frame(maxWidth: .infinity, alignment: .leading)
            }
        }
    }
}
  1. URL을 호출한다. data, response, error 등 정보를 읽고 정상적인 URl인지 체크한다.
  2. URL을 성공적으로 가져왔다면 해당 URL에서 데이터를 다운한다. 타입에 구애받지 않고 사용하기 위해 별도의 클로저 문을 만든다. downloadData 함수는 이스케이핑 클로저를 사용함으로써 비동기적으로 데이터를 받은 뒤 그 데이터를 처리할 수 있다.
  3. getPosts 함수에서 위 이스케이핑 클로저를 호출함으로써 데이터를 읽고 난 뒤에 데이터를 받았음이 보장된다. 데이터를 받았다면, 데이터를 디코딩한다.
  4. PostModel 구조체 데이터를 디코딩하는 과정은 Codable 프로토콜을 사용한 뒤 JSONDecoder을 통해 불러오면 된다.
  5. 디코딩한 데이터를 UI가 사용하는 데이터로 주는 작업은 백그라운드 스레드가 아니라 메인 스레드에서 진행해야 한다. 이때 디스패치 큐는 강한 참조를 하지 않도록 약한 참조로 선언, 사용자의 중도 중단 등 발생 가능한 취소 상황을 대비한다.
profile
JUST DO IT

0개의 댓글