1

My class was tasked with making an app that implements RESTful API calls. My app has 2 screens:

  1. The first screen that lists all the Pokemon types (in the form of parsed JSON)
  2. The second screen that lists details (in the form of parsed JSON) about the selected type

In our writeup for the project, we are tasked with indicating a code snippet with the "background thread." I'm not sure where the "background thread" is located in my code.

I found this StackOverflow answer, which indicates that code within DispatchQueue.global(qos: .background).async {} is run in the background thread while code within DispatchQueue.main.async {} is run in the main thread. However, I only have DispatchQueue.main.async {} and no DispatchQueue.global(qos: .background).async {}. Does this mean I have no background thread? Do I now have to implement it? Or is everything that is not in the main thread, running in the background thread by default?

The code for ViewController.swift, which lists all the Pokemon types, is below:

import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    final let url = URL(string: "https://pokeapi.co/api/v2/type/")
    private var results = [Movetype]()
    @IBOutlet var tableView: UITableView!
    var sequeIdentifiers = ["Normal", "Fighting", "Flying", "Poison", "Ground", "Rock", "Bug", "Ghost", "Steel", "Fire", "Water", "Grass", "Electric", "Psychic", "Ice", "Dragon", "Dark", "Fairy", "Unknown", "Shadow"]

    override func viewDidLoad() {
        super.viewDidLoad()
        downloadJson()
        // Do any additional setup after loading the view, typically from a nib.
    }

    func downloadJson() {
        guard let downloadURL = url else { return }

        URLSession.shared.dataTask(with: downloadURL) { data, urlResponse, error in
            guard let data = data, error == nil, urlResponse != nil else {
                print("something is wrong")
                return
            }
            print("downloaded")
            do
            {
                let decoder = JSONDecoder()
                let downloaded_movetypes = try decoder.decode(Results.self, from: data)
                self.results = downloaded_movetypes.results
                DispatchQueue.main.async { // <- ********* MAIN THREAD *********
                    self.tableView.reloadData()
                }
            } catch {
                print("something wrong after downloaded")
            }
        }.resume()
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return results.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "MovetypeCell") as? MovetypeCell else { return UITableViewCell() }

        cell.Name_Label.text = results[indexPath.row].name.capitalized

        let img_name = sequeIdentifiers[indexPath.row].lowercased() + ".png"

        print(img_name)

        cell.Img_View.image = UIImage(named: img_name)

        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        //performSegue(withIdentifier: sequeIdentifiers[indexPath.row], sender: self)
        let detailVc = self.storyboard?.instantiateViewController(withIdentifier: "Fighting")  as! Fighting
        detailVc.subType = sequeIdentifiers[indexPath.row].lowercased()
        self.navigationController?.pushViewController(detailVc, animated: true)
    }

}

Which part of that is the background thread?

The code for my app's second screen, Fighting.swift, which lists the details about the selected type, is below:

 var subType = ""
    override func viewDidLoad() {
        super.viewDidLoad()
        self.title = subType.capitalized
        let jsonUrlString = "https://pokeapi.co/api/v2/type/\(subType)/"
        guard let url = URL(string: jsonUrlString) else { return }

        URLSession.shared.dataTask(with: url) { (data, response, err) in

            guard let data = data else { return }

            do {
                // Get fighting JSON data
                let fighting_json = try JSONDecoder().decode(fighting.self, from: data)
                let detail_json = try JSONDecoder().decode(detailData.self, from: data)

                 print("Codementor == \(detail_json.damage_relations)")

                print(fighting_json.damage_relations?.double_damage_from?.compactMap({ $0.name?.capitalized }) ?? "Bad data")

                // Create Array: Double Damage From
                let double_damage_from_fighting_array = fighting_json.damage_relations?.double_damage_from?.compactMap({ $0.name?.capitalized }) ?? ["Bad data"]

                // Create Array: Double Damage To
                let double_damage_to_fighting_array = fighting_json.damage_relations?.double_damage_to?.compactMap({ $0.name?.capitalized }) ?? ["Bad data"]

                // Create Array: Half Damage From
                let half_damage_from_fighting_array = fighting_json.damage_relations?.half_damage_from?.compactMap({ $0.name?.capitalized }) ?? ["Bad data"]

                // Create Array: Half Damage To
                let half_damage_to_fighting_array = fighting_json.damage_relations?.half_damage_to?.compactMap({ $0.name?.capitalized }) ?? ["Bad data"]

                // Create Array: No Damage From
                let no_damage_from_fighting_array = fighting_json.damage_relations?.no_damage_from?.compactMap({ $0.name?.capitalized }) ?? ["Bad data"]

                // Create Array: No Damage To
                let no_damage_to_fighting_array = fighting_json.damage_relations?.no_damage_to?.compactMap({ $0.name?.capitalized }) ?? ["Bad data"]

                DispatchQueue.main.async { // <- ***** MAIN THREAD ******

                    // Print Label: Double Damage From
                    self.double_damage_from_fighting_Label.text = double_damage_from_fighting_array.joined(separator: ", ")

                    // Print Label: Double Damage To
                    self.double_damage_to_fighting_Label.text = double_damage_to_fighting_array.joined(separator: ", ")

                    // Print Label: Half Damage From
                    self.half_damage_from_fighting_Label.text = half_damage_from_fighting_array.joined(separator: ", ")

                    // Print Label: Half Damage To
                    self.half_damage_to_fighting_Label.text = half_damage_to_fighting_array.joined(separator: ", ")

                    // Print Label: No Damage From
                    self.no_damage_from_fighting_Label.text = no_damage_from_fighting_array.joined(separator: ", ")

                    // Print Label: No Damage To
                    self.no_damage_to_fighting_Label.text = no_damage_to_fighting_array.joined(separator: ", ")

                }

            } catch let jsonErr {
                print("Error serializing json:", jsonErr)
            }
            }.resume()
    }
}

Which part of that code is the background thread?

I know that you typically only need to call the main thread when changing the UI. Does that mean everything in the do{} statements in my examples is in the background thread, except for the code within DispatchQueue.main.async {}?

velkoon
  • 871
  • 3
  • 15
  • 35

2 Answers2

9

Whenever you do an API call and there is a "completion handler", then it is asynchronous. As it is asynchronous, you don't have to bother placing it in a thread, the framework does it for you.

For example if you place some sort of circular progress bar on your view, or anything that is animated, you will see that your call to URLSession.shared.dataTask won't stop the animation.

You still have to call DispatchQueue.main.async because when entering the completion handler code, you are on the thread that the dataTask created.

Scaraux
  • 3,841
  • 4
  • 40
  • 80
2

All the request and code inside

URLSession.shared.dataTask(with: url) { (data, response, err) in

runs in background thread , other DispatchQueue.main.async should be UI only and you set it correctly , you use this structure

DispatchQueue.global(qos: .background).async {
    print("This is run on the background queue")

    DispatchQueue.main.async {
        print("This is run on the main queue, after the previous code in outer block")
    }
}

when you want to run a long local/remote task that you want it to be in a background thread ( here global queue ) and it'll be equivalent to your current code but it will make the automatic switch to the background queue without explicitly doing it and it's the benefit of URLSession.shared.dataTask


consider this code inside viewDidLoad

 let url = URL(string: image.url)
 let data = try? Data(contentsOf: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
 imageView.image = UIImage(data: data!)

will block main thread as Data(contentsOf runs in the thread you put it in so you have 2 options

1-

DispatchQueue.global(qos: .background).async {
    print("This is run on the background queue")

      let url = URL(string: image.url)
      let data = try? Data(contentsOf: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch

    DispatchQueue.main.async {
        print("This is run on the main queue, after the previous code in outer block")
        imageView.image = UIImage(data: data!)

    }
}

2- Use your current code to load the data of the image

URLSession.shared.dataTask(with: url) { (data, response, err) in

        guard let data = data else { return }

        DispatchQueue.main.async {
        print("This is run on the main queue, after the previous code in outer block")
           imageView.image = UIImage(data: data!)

         }
   }.resume() 

so before using any method you should previously know where it will run in your current thread that if the main it will be blocked until the request finishes , and if not then wrap it's callback insid DispatchQueue.main.async` for any UI updates

Shehata Gamal
  • 98,760
  • 8
  • 65
  • 87