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

Project9: Purple warnings about using things outside the main thread.

Forums > 100 Days of Swift

It seems that forum posts in the classic "100 days of Swift" forum don't get many responses. But still, I'll try again.

I'm trying to follow the instructions for Project9, where we are basically modifying our code from Project7 to use threading.

I believe I have followed all the instructions to modify the code as Paul suggested, but now I am getting 2 purple warnings about things that should only be used in the main thread when I try to run my code.

My final code for my ViewController class looks like this...

class ViewController: UITableViewController {
    var petitions = [Petition]()
    var filter = ""

    var filteredPetitions: [Petition] {
        if filter == "" {
            return petitions
        }  else {
            return petitions.compactMap({ petition in
                if petition.title.lowercased().contains(filter) {
                    return petition
                } else {
                    return nil
                }
            })
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        title = "White House Petitions"

        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Credits", style: .plain, target: self, action: #selector(showCredits))

        navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .search, target: self, action: #selector(showSearch))

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

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

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        let petition = filteredPetitions[indexPath.row]
        cell.textLabel?.text = petition.title
        cell.detailTextLabel?.text = petition.body
        return cell
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let vc = DetailViewController()
        vc.detailItem = filteredPetitions[indexPath.row]
        navigationController?.pushViewController(vc, animated: true)
    }

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

    @objc func showError() {
        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)
    }

    @objc func showCredits() {
        let ac = UIAlertController(title: "Credits", message: "Data shown in this app was downloaded from the \"We The People\" petitions API of the United States White House, previous to the website being shut down." , preferredStyle: .alert)
        ac.addAction(UIAlertAction(title: "OK", style: .default))
        present(ac, animated: true)
    }

    @objc func showSearch() {
        let ac = UIAlertController(title: "Find Results Containing", message: nil, preferredStyle: .alert)
        ac.addTextField()

        let searchAction = UIAlertAction(title: "Search", style: .default) { [weak self, weak ac] _ in
            guard let filter = ac?.textFields?[0].text else { return }
            self?.filter = filter.lowercased()
            self?.tableView.reloadData()
        }

        ac.addAction(searchAction)
        present(ac, animated: true)
    }

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

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

The warnings I'm getting are in this segment in the fetchJSON function...

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

-[UINavigationController tabBarItem] must be used from main thread only

and this line inside the parse function...

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

UITableViewController.tableView must be used from main thread only

I just don't know if this is something that I have done wrong, or something that I should just ignore and move on.

3      

Can you confirm if putting the problem code inside DispatchQueue.main.async , still gives error, its important to not ignore this, the main thread is where any code for UI change must operate as per compiler , so you get this error, you can use DispatchQueue.main.async to ensure code is on main thread ...

@objc func fetchJSON() {
        let urlString: String
        DispatchQueue.main.async {

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

3      

I could do that, but that kind of defeats the purpose of calling the fetchJSON() function on the background thread in the first place.

So, to get rid of the warning completely, I would probably have to move the code that checks which tab is currently selected into its own function that runs on the main thread and returns the appropriate URL, and then pass the URL into the fetchJSON() function as a parameter. But that isn't the way that Paul does it in the project instructions.

3      

@FlyOstrich has the Purple bug:

2 purple warnings about things that should only be used in the main thread when I try to run my code ...snip.... it defeats the purpose of calling the fetchJSON() function on the background thread

I will be honest: I have not thoroughly reviewed your code. But this part in the func fetchJSON() sticks out to me:

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

The warning tells you that this can only run on the Main thread, yet you're calling it in a background thread.

    [UINavigationController tabBarItem] must be used from main thread only

Think of an extreme case. You want to fetch JSON, so you call this method and put it in the background. Maybe it takes 3 hours before that background thread gets executed. In the meantime, your user has selected a different tab bar item in the user interface. So you're asking your background thread to determine which tab bar the user has selected.

Well, when should the background thread make this decision? When the fetchJSON() function was called 3 hours ago? Or right now when the code is getting ready to run?

3      

I understand the reasons that Xcode gives the warnings, and I know how to make them go away. But I guess I was just wondering why I get the warnings when I followed the instructions from the project. Like, if I was missing something, or if there was a reason why Paul showed us to do it the way that he did rather than the correct way. Or perhaps if there is just a bug in Xcode that causes the warnings to show even though they are actually irrellevant in these cases.

Anyway, I modified the code like this, and now the errors don't show. But it is quite a bit different from the code that Paul provided for the project now.

class ViewController: UITableViewController {
    var petitions = [Petition]()
    var filter = ""

    var filteredPetitions: [Petition] {
        if filter == "" {
            return petitions
        }  else {
            var filtered: [Petition] = []

            DispatchQueue.global(qos: .userInitiated).async {
                filtered = self.petitions.compactMap({ petition in
                    if petition.title.lowercased().contains(self.filter) {
                        return petition
                    } else {
                        return nil
                    }
                })
            }

            return filtered
        }
    }

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

    override func viewDidLoad() {
        super.viewDidLoad()

        title = "White House Petitions"

        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Credits", style: .plain, target: self, action: #selector(showCredits))

        navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .search, target: self, action: #selector(showSearch))

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

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

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        let petition = filteredPetitions[indexPath.row]
        cell.textLabel?.text = petition.title
        cell.detailTextLabel?.text = petition.body
        return cell
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let vc = DetailViewController()
        vc.detailItem = filteredPetitions[indexPath.row]
        navigationController?.pushViewController(vc, animated: true)
    }

    @objc func fetchJSON(from urlString: String) {
        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)
    }

    @objc func showError() {
        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)
    }

    @objc func showCredits() {
        let ac = UIAlertController(title: "Credits", message: "Data shown in this app was downloaded from the \"We The People\" petitions API of the United States White House, previous to the website being shut down." , preferredStyle: .alert)
        ac.addAction(UIAlertAction(title: "OK", style: .default))
        present(ac, animated: true)
    }

    @objc func showSearch() {
        let ac = UIAlertController(title: "Find Results Containing", message: nil, preferredStyle: .alert)
        ac.addTextField()

        let searchAction = UIAlertAction(title: "Search", style: .default) { [weak self, weak ac] _ in
            guard let filter = ac?.textFields?[0].text else { return }
            self?.filter = filter.lowercased()
            self?.tableView.reloadData()
        }

        ac.addAction(searchAction)
        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      

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.