2

I have problem with NSProgress. The problem is that NSProgressIndicator is not updating during the process and showing only a small completed portion at the end of process. The localizedDescription is also showing only at the end of the process, but as 100% completed.

So, I have a class with one method findRepeatsWithProgressReporting using NSProgress

class TestProgress: NSObject, ProgressReporting
{
    let progress: Progress

    override init()
    {
        progress = Progress()
        super.init()
    }

    func findRepeatsWithProgressReporting(stringToSearch: String, minimalLength: Int, maximalLength: Int) -> [String]
    {
        var arrayOfRepeats = [String]()

        progress.totalUnitCount = Int64((minimalLength...maximalLength).count)

        for i in minimalLength...maximalLength
        {
            let arrayOfStrings = stringToSearch.chopString(stringOut: stringToSearch, length: i)
            let arrayOfUniqueStrings = Array(Set(arrayOfStrings))

            for each in arrayOfUniqueStrings
            {
                let arrayOfNSRanges = stringToSearch.searchForNSRangesOfStringInString(stringOut: stringToSearch, stringIn: each)

                var positions = String()

                if arrayOfNSRanges.count > 1
                {
                    for each1 in arrayOfNSRanges
                    {
                        let repeatStart = String(each1.location + 1)
                        let repeatEnd = String(each1.location + each1.length)
                        positions += "(" + repeatStart + "-" +  repeatEnd + ")"
                    }
                    let stringToShow = each + " " + positions
                    arrayOfRepeats.append(stringToShow)
                }
            }
            progress.completedUnitCount += 1
        }
        return  arrayOfRepeats
    }

}

Then, in myVewContrloler I have parentProgress repeatsProgress having totalUnitCount: 10 and have added the task of the method findRepeatsWithProgressReporting as childProgress to the parentProgress repeatsProgress using repeatsProgress.becomeCurrent(withPendingUnitCount: 10).

private var progressObservationContext = 0

class myVewContrloler: NSViewController
{
    ...

    var testProgress = TestProgress ()
    var repeatsProgress = Progress() 

    @IBOutlet weak var repeatsSearchProgressBar: NSProgressIndicator!
    @IBOutlet weak var repeatsPercentText: NSTextField!

    @IBOutlet weak var minimalLength: NSTextField!
    @IBOutlet weak var maximalLength: NSTextField!
    @IBOutlet var foundRepeats: NSTextView!

    @IBAction func actionFindRepeats(_ sender: AnyObject)
    {
        repeatsProgress = Progress(totalUnitCount: 10)

        let options : NSKeyValueObservingOptions = [.new, .old, .initial, .prior]
        repeatsProgress.addObserver(self, forKeyPath: "fractionCompleted", options: options, context: &progressObservationContext)
        repeatsProgress.addObserver(self, forKeyPath: "localizedDescription", options: options, context: &progressObservationContext)

        var arrayOfRepeats = [String]()

        repeatsProgress.becomeCurrent(withPendingUnitCount: 10)
        arrayOfRepeats = testProgress.findRepeatsWithProgressReporting(stringToSearch: stringToSearch, minimalLength: minimalLength.integerValue, maximalLength: maximalLength.integerValue)

        ...

        repeatsProgress.removeObserver(self, forKeyPath: "fractionCompleted")
        repeatsProgress.removeObserver(self, forKeyPath: "localizedDescription")

        repeatsProgress.resignCurrent()
    }
}

The last part is for KVO :

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
{
        guard context == &progressObservationContext else {
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
        return
    }

    if keyPath == "fractionCompleted"
    {
        OperationQueue.main.addOperation{
                let progress = object as! Progress
                self.repeatsSearchProgressBar.doubleValue = progress.fractionCompleted
                self.repeatsPercentText.stringValue = progress.localizedDescription
        }
    }
}

I have added

print("Observed Something")

inside of the

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
{  ...

and what I see is two times printing the "Observed Something"immediately after start and six times at the end, no printing in between (as it expected to be for the updating process). What can be the reason ?

VYT
  • 1,071
  • 19
  • 35

1 Answers1

4

This seems like a concurrency problem. Since func actionFindRepeats(_ sender: AnyObject) is running in the main thread, it's concurring with the UI updates, which affects the NSProgressIndicator directly.

See the last example of that answer for more details about that:

You can try adding all the content of your actionFindRepeats function into that block and see if it works:

DispatchQueue.global().async {
    // qos' default value is ´DispatchQoS.QoSClass.default`
}

Reference for that block:

Community
  • 1
  • 1
VitorMM
  • 1,060
  • 8
  • 33
  • Thanks for the advice! I have made a test project only for the described above task, searching for repeats in string with progress reporting. All is done in AppDelegate. I have added as you suggested DispatchQueue.global().async and now in this project I see completed NSProgressIndicator and 100% completed localizedDescription at the end of the process. In main project, where I have separate Controller for the same task, and still the same incomplete NSProgressIndicator. And, in both projects I don't have updating NSProgressIndicator, no intermediate percentage. – VYT Mar 24 '17 at 20:04
  • And now in both projects, after the changes I have made, I also have the following waring in test project: 2017-03-24 19:39:55.202 Test2[3926:127626] Uncommited CATransaction. Set CA_DEBUG_TRANSACTIONS=1 in environment to debug. CoreAnimation: warning, deleted thread with uncommitted CATransaction; set CA_DEBUG_TRANSACTIONS=1 in environment to log backtraces. – VYT Mar 24 '17 at 20:05
  • I see that your advice was a right key for the problem, so thanks again, and I accept your answer. – VYT Mar 25 '17 at 15:38