top of page
Search
  • Writer's pictureQuinne Farenwald

Navigating Swift Data Fetching: A Comprehensive Guide

Updated: Dec 5, 2023

Fetching and decoding JSON data is a common task in Swift development, and understanding the process is crucial for building robust applications. In this blog post, we'll explore two popular methods for fetching data in Swift—using URLSession for networking and leveraging the Combine framework for a reactive approach. Before diving into these techniques, let's start by understanding how to decode JSON data into Swift structs.


Understanding JSON Data and Creating Structs:

JSON (JavaScript Object Notation) is a lightweight data interchange format that is easy for humans to read and write. When working with JSON in Swift, we often create Swift structs or classes to model the data in a type-safe manner. This allows us to take full advantage of Swift's strong typing and ensures that our code is clean, maintainable, and less error-prone.


Example JSON Data:

Consider the following JSON data representing a list of petitions:

{ 
   "results": [ 
      { 
        "id": "2722358", 
        "type": "petition", 
        "title": "Remove John Smith" 
       } 
    ] 
}

Validate URL and Craft Swift Structs

Before diving into Swift code, it's essential to validate that the URL endpoint returns the expected JSON data. Leveraging a third-party tool simplifies this process, ensuring you can hit the endpoint and inspect the response effortlessly. My personal recommendation is Postman, a powerful API development and testing tool available at Postman.


Using Postman for Validation

  1. Endpoint Validation: Input the URL into Postman and execute a request to the endpoint. This step confirms that the endpoint is functioning as expected and returns the JSON data you anticipate.

  2. Request Verification: With Postman, you can inspect the headers, status codes, and raw response body. Ensure that the response aligns with your expectations and contains the necessary data.

Now that you've validated the URL and understand how to call the endpoint successfully, you're ready to create Swift structs to map the received JSON data. This step ensures a seamless transition from endpoint validation to Swift code implementation.

Creating Swift Structs:

To work with this JSON data, we create Swift structs that mirror its structure. Let's break down the process:

struct Petition: Codable { 
   var id: String 
   var type: PetitionType
   var title: String 
} 

struct Petitions: Codable { 
   var results: [Petition] 
}

In this example:

  • Petition is a struct representing an individual petition with fields corresponding to the JSON keys.

  • Petitions is a struct representing the top-level structure with an array of Petition objects.

  • 'Codable' allows us to encode and decode Swift types to and from external representations, making it especially useful for handling JSON responses.

Now that we've created our Swift structs, let's proceed to explore the two methods of fetching data: URLSession for networking and the Combine framework for a reactive approach.


URLSession (Networking):

URLSession is a fundamental framework for making network requests. It provides different tasks for various use cases, such as fetching data or downloading files.


Here's a basic example using URLSession:

guard let url = URL(string: "https://api.example.com/data") else { return }

URLSession.shared.dataTask(with: url) { data, response, error in
   if let error = error {
      print("Error: \(error.localizedDescription)") 
      return 
   } 
    
    // Parse data 
    if let data = data {
       do { 
          let decodedData = try JSONDecoder().decode(Petitions.self, from: data) 
          // Handle the decoded data 
       } catch { 
          print("Error decoding data: \(error.localizedDescription)") 
       } 
    } 
 }.resume()

Use this URLSession in your code by creating a fetcher class:

import Foundation

class PetitionsFetcher {     
   private let baseURLString = "https://api.example.com/data"
   
   func fetchPetitions(completion: @escaping (Petitions?) -> Void) {         
      guard  let url = URL(string: baseURLString) else {             
         completion(nil)             
         return         
      }          
      
      URLSession.shared.dataTask(with: url) { data, response, error in
         if let error = error {                 
            print("Error: \(error.localizedDescription)")                 
            completion(nil)                 
            return             
         }              
         
         if let data = data {                 
            do {                     
               let decodedData = try JSONDecoder().decode(Petitions.self, from: data)                     
               completion(decodedData)                 
            } catch {                     
               print("Error decoding data: \(error.localizedDescription)")                      
               completion(nil)                 
            }             
         }         
      }.resume()     
   } 
}

Then call the fetcher in your code, perhaps in a view model:

let petitionsFetcher = PetitionsFetcher() 

petitionsFetcher.fetchPetitions { petitions in 
   if let petitions = petitions {         
      // Handle the fetched data
      print("Fetched Petitions: \(petitions.results)")     
   } else {         
      // Handle error or absence of data
      print("Failed to fetch petitions.")     
   } 
}

Combine Framework:

Combine is a declarative Swift framework for processing values over time. It integrates seamlessly with URLSession to create a more reactive and concise approach to handling asynchronous operations.


DataTaskPublisher:

Combine introduces URLSession.DataTaskPublisher, a publisher that combines networking calls with data processing in a streamlined manner.

Here's an example using Combine:

URLSession.shared.dataTaskPublisher(for: url) 
.map(\.data) 
.decode(type: MyModel.self, decoder: JSONDecoder()) .sink(receiveCompletion: { completion in 
   // Handle completion (success or failure) 
}, receiveValue: { decodedData in 
   // Handle the decoded data 
}) .store(in: &cancellables)

Combine enables you to chain operations, handle errors, and manage the lifecycle of your asynchronous tasks more efficiently.


Use this DataTaskPublisher in your code by creating a fetcher class:

import Foundation 
import Combine 

class PetitionsFetcher { 
   private let baseURLString ="https://api.example.com/data"
   private var cancellables: Set<AnyCancellable> = []
   
   func fetchPetitions() -> AnyPublisher<Petitions, Error> { 
      guard let url = URL(string: baseURLString) else { 
         return Fail(error: URLError(.badURL)).eraseToAnyPublisher() 
      }
      
      return URLSession.shared.dataTaskPublisher(for: url) 
         .map(\.data) 
         .decode(type: Petitions.self, decoder: JSONDecoder()) 
         .eraseToAnyPublisher() 
   } 
}

Then call the fetcher in your code, perhaps in a view model:

let petitionsFetcher = PetitionsFetcher()

petitionsFetcher.fetchPetitions() 
   .sink(receiveCompletion: { completion in 
      switch completion { 
      case .finished: 
         break 
      case.failure(let error): 
         // Handle error 
         print("Error: \(error.localizedDescription)") 
      } 
   }, receiveValue: { petitions in 
      // Handle the fetched data 
      print("Fetched Petitions: \(petitions.results)") 
   }) .store(in: &petitionsFetcher.cancellables)

In this example:

  • The PetitionsFetcher class now uses Combine to handle asynchronous operations.

  • The fetchPetitions function returns an AnyPublisher that emits Petitions or an error.

  • The sink operator is used to receive and handle the publisher's output and completion.


Practice

Here is some json practice data: https://www.hackingwithswift.com/samples/petitions-1.json along with a wonderful additional tutorial video: https://www.youtube.com/watch?v=9FriGMWIbdc


Choosing between URLSession and Combine for data fetching in Swift depends on your project requirements and familiarity with the frameworks. URLSession offers a traditional, imperative approach, while Combine provides a more declarative, reactive style. Consider your app's architecture, complexity, and your team's expertise to determine the best fit for your data fetching needs. Both approaches are powerful and can be tailored to suit various scenarios, so experiment with both and find the one that aligns best with your development style.

4 views0 comments

Commentaires


bottom of page