Downloading JSON Data with URLSession

Learn how to fetch and decode JSON data from a remote URL using URLSession and JSONDecoder in Swift, a fundamental skill for working with APIs.

Downloading JSON Data

Note:

This tutorial explains how to download data from websites or APIs using the URLSession framework and parse it with JSONDecoder.

Overview

A common task in mobile development is fetching data from a remote server or API. Typically, this data is formatted as JSON. In Swift, we use the URLSession API for networking and JSONDecoder to parse JSON data into our custom Swift types.

For this example, we'll fetch a JSON file that contains a list of news articles, each with a title and a story.

JSON Structure

Reference:

Step 1: Create the Data Model

First, we need to create a Swift struct that matches the structure of our JSON objects. By making our struct conform to the Decodable protocol, we can use JSONDecoder to automatically convert the JSON data into instances of our struct.

Create a new file News.swift:

// In News.swift
import Foundation

struct News: Decodable {
    let title: String
    let story: String
}

Step 2: Create a Networking Service

To make our code reusable, we'll create a new class or struct to handle the data downloading logic. This service will have a function that takes a URL and a completion handler. The @escaping keyword indicates that the completion handler will be called after the function returns, which is necessary for asynchronous operations.

// In a new file, e.g., WebService.swift
import Foundation

class WebService {
    func downloadNews(from url: URL, completion: @escaping ([News]?) -> ()) {
        // Networking code will go here
    }
}

Step 3: Fetch and Decode the Data

Inside our downloadNews function, we'll use URLSession.shared.dataTask to perform the network request.

  1. Create a data task with the specified URL.
  2. In the completion handler, check for errors.
  3. If data is received, use JSONDecoder().decode(_:from:) to try and parse it into an array of News objects.
  4. Call the function's completion handler with the decoded data (or nil if an error occurred).
  5. Don't forget to call .resume() on the data task to start it.
// Inside the WebService class

func downloadNews(from url: URL, completion: @escaping ([News]?) -> ()) {
    URLSession.shared.dataTask(with: url) { data, response, error in
        
        if let error = error {
            print("Error downloading data: \(error)")
            completion(nil)
            return
        }
        
        guard let data = data else {
            print("No data received.")
            completion(nil)
            return
        }
        
        do {
            let newsArray = try JSONDecoder().decode([News].self, from: data)
            completion(newsArray)
        } catch {
            print("Error decoding data: \(error)")
            completion(nil)
        }
        
    }.resume()
}

Step 4: Using the Networking Service

Now, from any ViewController, you can create an instance of WebService and call the downloadNews function. The results will be delivered asynchronously in the completion block.

// In your ViewController

class NewsViewController: UIViewController {
    private var newsArray = [News]()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let url = URL(string: "https://your-api-url.com/news.json")!
        
        WebService().downloadNews(from: url) { (news) in
            if let news = news {
                self.newsArray = news
                
                // Since this completion is on a background thread,
                // update UI on the main thread.
                DispatchQueue.main.async {
                    // self.tableView.reloadData()
                    print(self.newsArray)
                }
            }
        }
    }
}
Using the WebService

Note:

This example provides a solid foundation for working with any JSON API. Remember to always handle UI updates on the main thread.