UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Day 39 Project 9 - errors despite following the tutorial

Forums > 100 Days of Swift

Hi guys,

Maybe you can help me with this. Even though I followed the tutorial for multi-threading very carefully, I still have couple of errors. To break this down, I want to show you the code first:

@objc func fetchJSON() {
        let urlString: String

          if navigationController?.tabBarItem.tag == 0 {
              urlString = "https://www.hackingwithswift.com/samples/petitions-1.json"
          } else {
              urlString = "https://www.hackingwithswift.com/samples/petitions-2.json"
          }

              if let url = URL(string: urlString) {
                  if let data = try? Data(contentsOf: url) {
                      parse(json: data)
                      return
                  }
              }
        performSelector(onMainThread: #selector(showError), with: nil, waitUntilDone: false)
    }

There are 2 errors for this code:

  1. "-[UINavigationController tabBarItem] must be used from main thread only"
  2. "UIViewController.navigationController must be used from main thread only"

As for the 2nd part of code:

func parse(json: Data) {
        let decoder = JSONDecoder()

        if let jsonPetitions = try? decoder.decode(Petitions.self, from: json) {
            petitions = jsonPetitions.results
            initialPetitions = jsonPetitions.results

            tableView.performSelector(onMainThread: #selector(UITableView.reloadData), with: nil, waitUntilDone: false)
        }

The error here says: "UITableViewController.tableView must be used from main thread only"

Any hints on what I am doing wrong here? I followed Paul's tutorial step by step and these lines still appear.

4      

My guess is that this checker is just confused because you are using this from the main thread with the performSelector(onMainThread: call

4      

Hold on. According to the tutorial, fetchJSON() runs in the background, so the use of performSelector(onMainThread: seems fine here. However, the errors still persist... I'm confused.

4      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

What does your viewDidLoad look like?

4      

override func viewDidLoad() {
        super.viewDidLoad()

        navigationItem.leftBarButtonItem = UIBarButtonItem(title: "CREDITS", style: .plain, target: self, action: #selector(credits))
        let searchButton = UIBarButtonItem(barButtonSystemItem: .search, target: self, action: #selector(searchText))
        let clearFilterButton = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(clearFilter))
        navigationItem.rightBarButtonItems = [searchButton, clearFilterButton]

        performSelector(inBackground: #selector(fetchJSON), with: nil)
    }

4      

I went through multiple Reddit threads on this matter, it seems that for now it can't quite be explained why it happens. Went back to DispatchQueue method (shown in the tutorial from day 39.), works like a charm (well, with no errors at least). There have been rumours about the incoming discontinuation of performSelector method - I am not sure if that's true, but given the uncertainty of the method working in certain cases, for now I'm sticking with the DispatchQueue one.

4      

Hello, can you give me your facebook or telegram, i need your help with some exercises

4      

Sure thing, you can find me on Twitter as @dorianzet, you can shoot me a message there.

4      

@manan  

I am facing the same issues.

I think Swift would like us not to touch UI elements in a background thread at all. If necessary, it allows a dispatchQueue to the main thread, just like you said... works flawlessly. Use a perform selector and it throws those purple errors.

However, those errors don't seem to stop the working of the app and now seem like somewhat warnings.

Just Swift's preferences....

I've learnt to ignore them.

4      

Same issue, I think they changed their API after the tutorial was released.

4      

I got the same exact thing keep coming on, performSelector is defo easier to use then using lots of closures but, must be some solution for this, and from my side is also complainign about "UITableviewController.tableView must be used from main thread only", well .....tableView.performSelector(onMainThread: #selector(UITableView.reloadData), with: nil, waitUntilDone: false) is on the main thread but I have no idea why is keep complaining about this, does anyone have any idea about this ?

4      

DispatchQueue is still a good method to use is not hard. I have used that in project 7 well. Must be a trick around that perfomSelector method.

func submit(_ answer: String) {
    DispatchQueue.global(qos: .background).async { [ weak self ] in
        self?.filteredPetitions.removeAll(keepingCapacity: true)
    }

    for petition in petitions {
        if petition.title.contains(answer) { // If any of the petitions contains the users input data
            filteredPetitions.append(petition) // The number of matching petitions will be displayed to the user
            DispatchQueue.main.async { [ weak self ] in
                self?.tableView.reloadData()
            }
        }
    }
}

4      

The problem ultimately lies in viewDidLoad():

performSelector(inBackground: #selector(fetchJSON), with: nil)

This says to call fetchJSON() in the background, yet in fetchJSON, we access the UI:

if navigationController?.tabBarItem.tag == 0 {

And again in parse(json:)

tableView.performSelector(onMainThread: #selector(UITableView.reloadData), with: nil, waitUntilDone: false)

navigationController and tableView trigger the problem. Whatever lines access those UI elements need to be wrapped in a DispatchQueue.main.async block.

I solved it like this:

        var tabTag: Int = 0
        DispatchQueue.main.async {
            tabTag = (self.navigationController?.tabBarItem.tag)!
        }

        if tabTag == 0 {

and

            DispatchQueue.main.async {
                self.tableView.reloadData()
            }

I don't know if something changed in Swift since Paul wrote that project or what, but there you go.

4      

Did you use that in fetchJSON method? Also did you use that in combination with performSelector? I see u used a global variable there. Thats actually a very good idea tho, something must has changed, the app is running as normal but Swift is complaning.

4      

I also wrapped tableView into a DispatchQueue block but is still complaining.

func parse(json: Data) { let decoder = JSONDecoder() // This is responsable for converting JSON data into Swift code what we can use and display for users.

    if let jsonPetitions = try? decoder.decode(Petitions.self, from: json) { // Parsing data into Petitions array/ those instances will hold/title/body/signatureCount from JSON
        petitions = jsonPetitions.results // The parsed data is assigned to "patitions" array

        DispatchQueue.main.async { [weak self] in
            self?.tableView.reloadData()
    }
        performSelector(onMainThread: #selector(showError), with: nil, waitUntilDone: false)
    }
}

4      

Also guys I would suggest to try and filter data, my app is crashing at a certain point

4      

Here's what I did in fetchJSON and parse(json:)

    @objc func fetchJSON() {
        let urlString: String

        var tabTag: Int = 0
        DispatchQueue.main.async {
            tabTag = (self.navigationController?.tabBarItem.tag)!
        }

        if tabTag == 0 {
            //let urlString = "https://api.whitehouse.gov/v1/petitions.json?limit=100"
            urlString = "https://www.hackingwithswift.com/samples/petitions-1.json"
        } else {
            //let urlString = "https://api.whitehouse.gov/v1/petitions.json?signatureCountFloor=10000&limit=100"
            urlString = "https://www.hackingwithswift.com/samples/petitions-2.json"
        }

        if let url = URL(string: urlString) {
            if let data = try? Data(contentsOf: url) {
                parse(json: data)
                return
            }
        }

        performSelector(onMainThread: #selector(showError), with: nil, waitUntilDone: false)
    }

    func parse(json: Data) {
        let decoder = JSONDecoder()

        if let jsonPetitions = try? decoder.decode(Petitions.self, from: json) {
            petitions = jsonPetitions.results
            listPetitions = petitions
            DispatchQueue.main.async {
                self.tableView.reloadData()
            }
        } else {
            performSelector(onMainThread: #selector(showError), with: nil, waitUntilDone: false)
        }
    }

And it works without triggering the purple warning about accessing the UI on a background thread.

5      

Okay cool, did you trye to filter the petitions by using search? It worked a couple of times but when I was really stressing it it crashed a couple of times and I can't really tell why, I have the same solution in my code but is just weird something is not right.

4      

I also put the filter code on a background thread like this:

performSelector(inBackground: #selector(submit), with: nil)

4      

same problem with purple warnings about main thread https://imgur.com/DGHIoQz Tried everything from offered solutions, in result - app works, but xcode still throwing warnings. I changed code back to previous version with DispatchQueue blocks.

4      

Day 39 definitely needs revision. It simply doesnt work as written. Thanks to this forum. I was able to get it to work.

Xcode Version 13.4.1 (13F100)

Here are the changes i Had to make: (assume search petitions Challenge is done with search petitions array named as : var searchPetitions = [Petition]()

Given below is the corrected code: (not taking credit as its mostly due to other @roosterboy


//Change 1 in viewDidLoad:

 DispatchQueue.global(qos: .userInitiated).async {         
  [weak self] in
            if let url = URL(string: self!.urlString) {
                if let data = try? Data(contentsOf: url) {
                    // we're OK to parse!
                    self?.parse(json: data)
                    return
                }
            }
            self?.showError()

        }

     //showError and parse code needs to be changed:

     @objc func showError() {
        DispatchQueue.main.async {
            [weak self ] in
        let ac = UIAlertController(title: "Loading error", message: "There was a problem loading the feed; please check your connection and try again.", preferredStyle: .alert)
        ac.addAction(UIAlertAction(title: "OK", style: .default))
            self?.present(ac, animated: true)
        }
    }

    func parse(json: Data) {
            let decoder = JSONDecoder()

            if let jsonPetitions = try? decoder.decode(Petitions.self, from: json) {
                petitions = jsonPetitions.results
                DispatchQueue.main.async {
                    self.tableView.reloadData()
                }
            } else {
                performSelector(onMainThread: #selector(showError), with: nil, waitUntilDone: false)
            }
        }

        """

3      

Facing same issues. In a background thread, Swift would like us not to touch UI elements. The dispatchQueue can be dispatched to the main thread, as you mentioned... and it works as expected. The purple errors appear when you use a perform selector. While those errors don't seem to stop the app from working, they now seem like warnings. The preferences of Swift... It's become second nature to ignore them

3      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until April 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.