HirsuteJim
HirsuteJim

Reputation: 676

Swift 5: I Am Unable to Get My UITableView To Update In A Timely Manor (Using both `DispatchQueue.global().sync` and `DispatchQueue.main.async`

Swift 5: I Am Unable to Get My UITableView To Update In A Timely Manor (Using both DispatchQueue.global().sync and DispatchQueue.main.async

In a nutshell:

I've tried putting all the "work" on the background thread, I even created a thread with QoSClass.utility, and no matter what I do … it completely ignores all occurrences of DispatchQueue.main.async until the entire function has completed.

I have no problem getting done the work my code needs to accomplish. My challenge at this point is controlling the order in which things are happening and getting the display to update (AND remain responsive to the user, which is currently does NOT do).

EDIT/UPDATE:

Using the answer below … IF I use .async I get the timely updates that I desire BUT I don't get the index results in order 1, 2, 3 … 20. IF I use .sync I get the order that I want but NO updates. How do I get my loop to execute IN ORDER and GET UPDATES to display once per loop?

I am VERY NEW to threading here, so please type slowly. Thank you!

Here's my VERY STRIPPED DOWN code:

//
//  ValidateViewController.swift
//  MySpecialProject
//
//  Created by Jim Termini on 10/16/21.
//

import UIKit
import PDFKit
import UniformTypeIdentifiers
import CoreData

class ValidateViewController: UIViewController, PDFViewDelegate {

    @IBOutlet weak var tableView: UITableView!

    var statuses: [ValidationStatus] = []

    //🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠
    //🟠    START HERE: ViewDidLoad
    //🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠
    override func viewDidLoad() {
        super.viewDidLoad()

        statuses = createStatusArray()
        tableView.delegate = self
        tableView.dataSource = self
    }


    //🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠
    //🟠    Button Pressed — Start Validating the Document!!
    //🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠
    @IBAction func startValidatIn(_ sender: Any) {
        print("🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢")
        print("🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢")
        theMainThing()
        print("🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢")
        print("🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢")
    }


    //🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠
    //🟠    theMainThing function called when button pushed.
    //🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠🟠
    func theMainThing() {

        print("🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡")
        print(pickedDocSourceFPnFURL ?? "No File Picked")


        let openResults = openFile(pdfURLToSearch: pickedDocSourceFPnFURL!)
        originalPDFDocument = openResults.0
        metaPDFModifiedDate = openResults.1

        var unaccountedPageCount = orginalDocumentPageCount


        // Keep track of the total number of Forms found in the Document
        var theFormCount = 0

        let JimmyQoS: DispatchQoS.QoSClass = DispatchQoS.QoSClass.utility
        let JimmysQue: DispatchQueue = DispatchQueue.global(qos: JimmyQoS)
        var countOfFormsOfThisPageLength: Int = 0
        var dummyVar: Int = 0


        for index in 1...20 {
            JimmysQue.sync {

                print("🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡")

                if index == 6 {
                    //
                    //  Do special case stuff when index = 6
                    //
                } else if index == 1 {
                    //
                    //  Do special case stuff when index = 6
                    //
                } else {
                    //
                    //  Do regular case stuff when index = 2 – 5, 7 – 20.
                    //
                }

                print("🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴")
                print("The NEXT Line is \"DispatchQueue.main.async\" SHOULD see GREEN row NEXT!!")


                DispatchQueue.main.async  {
                    print("🟢🟢🟢\(Date())🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢")
                    print("Here I am ... INSIDE the main thread async.")

                    if countOfFormsOfThisPageLength > 0 {
                        let term = (countOfFormsOfThisPageLength > 1) ? "forms" : "form"
                        self.statuses.append((ValidationStatus(image: StatusIcon.allGood,  title: "Identified \(countOfFormsOfThisPageLength) \(term) of \(index) pages each.", details: "Unaccounted for Pages: \(dummyVar) of \(self.orginalDocumentPageCount)")))
                        self.tableView.reloadData()
                        self.tableView.setNeedsDisplay()
                    }

                    print("This SHOULD be causing my display to update.")
                    print("🟢🟢🟢\(Date())🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢")
                }


                print("🟣🟣🟣\(Date())🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣")
                print("Here I am ... about to sleep the global (background) thread for 5 seconds to ensure the TableView is updated and displayed properly.")
                sleep(5)
                print("And here I am ... 5 seconds later ... still no UIViewTable Update :-(")
                print("🟣🟣🟣\(Date())🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣")
            }
        }
    }
}
   

Here is the walk-thru of what does and should happen…

And this is my ACTUAL console output:

The green lines, from the high priority main thread, are not executing until AFTER the for index in 1…20 loop completes + AFTER the theMainThing() function completes + AFTER the startValidatIn(_ sender: Any) function completes‼️

🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢
🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢

🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡

🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡
🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴
The NEXT Line is "DispatchQueue.main.async" SHOULD see GREEN row NEXT!!
🟣🟣🟣2021-10-17 02:34:21 +0000🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣
Here I am ... about to sleep the global (background) thread for 5 seconds
to ensure the TableView is updated and displayed properly.
And here I am ... 5 seconds later ... still no UIViewTable Update :-(
🟣🟣🟣2021-10-17 02:34:26 +0000🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣


-------------------------
18 Duplicate Sets Omitted
-------------------------


🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡
🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴
The NEXT Line is "DispatchQueue.main.async" SHOULD see GREEN row NEXT!!
🟣🟣🟣2021-10-17 02:34:26 +0000🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣
Here I am ... about to sleep the global (background) thread for 5 seconds
to ensure the TableView is updated and displayed properly.
And here I am ... 5 seconds later ... still no UIViewTable Update :-(
🟣🟣🟣2021-10-17 02:34:31 +0000🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣

🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢
🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢

🟢🟢🟢2021-10-17 02:46:34 +0000🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢
Here I am ... INSIDE the main thread async.
This SHOULD be causing my display to update.
🟢🟢🟢2021-10-17 02:46:34 +0000🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢
🟢🟢🟢2021-10-17 02:46:34 +0000🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢
Here I am ... INSIDE the main thread async.
This SHOULD be causing my display to update.
🟢🟢🟢2021-10-17 02:46:34 +0000🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢

This is what my console output SHOULD be:

After every red line should be a green line from the high priority main UI thread.

🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢
🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢
🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡🔴🟡

🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡
🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴
The NEXT Line is "DispatchQueue.main.async" SHOULD see GREEN row NEXT!!
🟢🟢🟢2021-10-17 02:46:34 +0000🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢
Here I am ... INSIDE the main thread async.
This SHOULD be causing my display to update.
🟢🟢🟢2021-10-17 02:46:34 +0000🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢
🟣🟣🟣2021-10-17 02:34:21 +0000🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣
Here I am ... about to sleep the global (background) thread for 5 seconds
to ensure the TableView is updated and displayed properly.
And here I am ... 5 seconds later ... still no UIViewTable Update :-(
🟣🟣🟣2021-10-17 02:34:26 +0000🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣

🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡🟡
🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴
The NEXT Line is "DispatchQueue.main.async" SHOULD see GREEN row NEXT!!
🟢🟢🟢2021-10-17 02:46:34 +0000🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢
Here I am ... INSIDE the main thread async.
This SHOULD be causing my display to update.
🟢🟢🟢2021-10-17 02:46:34 +0000🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢🟢
🟣🟣🟣2021-10-17 02:34:26 +0000🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣
Here I am ... about to sleep the global (background) thread for 5 seconds
to ensure the TableView is updated and displayed properly.
And here I am ... 5 seconds later ... still no UIViewTable Update :-(
🟣🟣🟣2021-10-17 02:34:31 +0000🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣🟣

🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢
🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢🟣🟢

Upvotes: 0

Views: 101

Answers (1)

Scott Thompson
Scott Thompson

Reputation: 23701

When the user clicks the button, you are on the Main Thread; it's handling that button click event.

While handling that event, you then create a queue JimmysQue and execute a synchronous operation on it with JimmysQue.sync { ... }.

By calling sync you are saying "I want to perform this operation on another queue, but I also want to (synchronously) wait until that other queue is done. In other words you've put the Main Thread on pause until the block you've dispatched to JimmysQue completes.

When running that background action, JimmysQue calls DispatchQueue.main.async. A conceptual model you might associate with this is "send an event to the main event event loop that will run this block of code when no other events are being handled."

But remember, the Main Thread is still handling your click event! You pasused it until JimmysQue could finish running its block. So that thing you've dispatched to the main thread won't run until your button handler finishes and your button handler is still waiting on the rest of the activity you've sent to JimmysQue.

The simple change that I think will get you the effect you're after is to dispatch your block to JimmysQue using async instead of sync.

The validation block will be sent off to run in the background. The Main Thread won't wait on it (the button click event will finish and the Main Thread will go off to handle any other events).

Then when the block on JimmysQue finishes validating it will send the block using DispatchQueue.main.async to be executed on the Main Thread when it has some free time.

I... typed... this... very... slowly...

Here's an even more stripped down example that demonstrates the general flow. You can paste this in a Playground. Try changing the call from async to sync on the .utility queue to see how the behavior changes.

import Foundation

func theMainThing() {
    print("Dispatching blocks from main thread")

    for index in 1...20 {
        DispatchQueue.global(qos: .utility).async {

            // Sleep for a random amount of time to simulate a long
            // running Action
            sleep((1...5).randomElement()!)

            DispatchQueue.main.async  {
                print("The main thing's index number \(index) completed")
            }
        }
    }

    print("The Main Thread has gone on to bigger and better things")
}

theMainThing()

In comments you asked about ordering the completion of the results. Here's an example of that:

import Foundation

    func theMainThing() {
        print("Dispatching Async blocks from main thread")

        let scottsQueue = DispatchQueue(label: "ValidationQueue", qos: .utility)

        for index in 1...20 {
            scottsQueue.async {
                print("The starting item \(index)")

                // Sleep for a random amount of time to simulate a long
                // running Action
                sleep((1...5).randomElement()!)
                DispatchQueue.main.sync {
                    print("The main thing's index number \(index) completed")
                }
            }
        }

        print("The Main Thread has gone on to bigger and better things")
    }

    theMainThing()

Note that we put all the validation blocks into a serial queue so they are executed strictly in order. Each block dispatches to the main queue using sync so each background task waits for the Main Queue to acknowledge the completion before it completes.

Upvotes: 1

Related Questions