Day 39 - 100 Days of Swift

3 minute read

Project 9 (part 1)

Day 39 is the first part of the ninth project. It is a technique project about Grand Central Dispatch and basic concurrency. You make some edits to project 7 that makes the petitions load on a background thread instead of locking up the UI. And you dispatch the reloading of the tableview and the showing of any error messages back to the main thread. You also briefly look at performSelector(inBackground:) and refactor the code to use it.

First, you dispatch the loading of data to a background thread with DispatchQueue.global(sos:):

1
2
3
4
5
6
7
8
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
    if let url = URL(string: urlString),
        let data = try? Data(contentsOf: url) {
        self?.parse(json: data)
        return
    }
    self?.showError()
}

This loads and parses the JSON on a background thread, leaving the main thread unblocked to respond to user interactions, but it tries to update the UI on the background, which is not allowed. So you have to dispatch that back to the main queue like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Inside presentInformationalAlert
DispatchQueue.main.async { [weak self] in
    let alertController = UIAlertController(title: title,
                                            message: message,
                                            preferredStyle: .alert)
    let action = UIAlertAction(title: "Ok",
                               style: .default)
    alertController.addAction(action)
    self?.present(alertController, animated: true)
}

// Inside parse(json:)
DispatchQueue.main.async { [weak self] in
    self?.tableView.reloadData()
}

He then shows you how to refactor the same process using performSelector(inBackground:). I didn’t do it, because I prefer the other method, but here is what it would look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
override func viewDidLoad() {
    super.viewDidLoad()

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

@objc func fetchJSON() {
    let urlString: String

    if navigationController?.tabBarItem.tag == 0 {
        urlString = "https://api.whitehouse.gov/v1/petitions.json?limit=100"
    } else {
        urlString = "https://api.whitehouse.gov/v1/petitions.json?signatureCountFloor=10000&limit=100"
    }

    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
    tableView.performSelector(onMainThread: #selector(UITableView.reloadData),
                              with: nil,
                              waitUntilDone: false)
} else {
    performSelector(onMainThread: #selector(showError),
                    with: nil,
                    waitUntilDone: false)
}

}

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

With all that work, the app looks exactly the same, other than the UI doesn’t freeze while it is loading the petitions, but instead shows an empty list until they load:

Screenshots of working app

You can find my version of this project at the end of day 39 on Github here.

Reflections

Concurrency is a very difficult topic. I’m glad that he is slowly introducing it in a way that is easy to understand. Also, I’ve never used DispatchQueue.global or performSelector(inBackground:) before, so it was cool to use some different parts of the GCD API than I normally use.