WWDC22 SALE: Save 50% on all my Swift books and bundles! >>

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.

1      

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

1      

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.

1      

Hacking with Swift is sponsored by Emerge

SPONSORED Optimize your app’s startup time, binary size, and overall performance using Emerge’s advanced app optimization and monitoring tools. Reliably measure app size, speed up your app's startup time with Emerge's Launch Booster, and much more. Emerge is actively used by many of the top mobile development teams in the world.

Find out more

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

What does your viewDidLoad look like?

1      

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)
    }

1      

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.

1      

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

1      

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

1      

@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.

1      

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

1      

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 ?

1      

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()
            }
        }
    }
}

1      

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.

1      

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.

1      

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)
    }
}

1      

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

1      

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.

2      

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.

1      

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

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

1      

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.

1      

Hacking with Swift is sponsored by Emerge

SPONSORED Optimize your app’s startup time, binary size, and overall performance using Emerge’s advanced app optimization and monitoring tools. Reliably measure app size, speed up your app's startup time with Emerge's Launch Booster, and much more. Emerge is actively used by many of the top mobile development teams in the world.

Find out more

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

Reply to this topic…

You need to create an account or log in to reply.

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.