ProgressBar with DispatchQueue

I have searched but cannot find an example of this.

In my example I use playground to nest for-loops in a function called from within DispatchQueue.

I find many roadblocks:

//import AppKit   // playground doesn't recognize AppKit?!
import Dispatch
import Foundation


var comboCount:NSKeyValueObservation?

//class viewContr: NSViewController {
    @IBOutlet weak var progressBar: NSProgressIndicator!
//    deinit { comboCount?.invalidate() }
    
    @IBAction func calcStuff() {
        DispatchQueue.global(qos: .userInitiated).async {
            let combos =  possibleCombos()
            DispatchQueue.main.async {
                for word in combos {
                    word.print0b8()
                }
            }
        }
    }
//}

func possibleCombos() -> [Int8] {
    var combos = [Int8]()
    for a in [0, 1] {
        for b in [0, 1] {
            for c in [0, 1] {
                for d in [0, 1] {
                    combos.append(Int8(d | (c << 1) | (b << 2) | (a << 3)) )
                    comboCount = combos.count/16 as! NSKeyValueObservation
                    // known total combos = 16
                    // How do I pass combos.count out to a progress bar?
                }
            }
        }
    }
    return combos
}

extension Int8 {func print0b8() {print("0b" + pad(string: String(self, radix: 2), toSize: 4))}}

func pad(string : String, toSize: Int) -> String {var padded = string;for _ in 0..<(toSize - string.count) {padded = "0" + padded};return padded}

Removing the @IB obstacles and the attempted declaration of a ViewController does produce output, but can't be used with a progress bar, e.g.,

import Dispatch
import Foundation

DispatchQueue.global(qos: .userInitiated).async {
    let combos =  possibleCombos()
    DispatchQueue.main.async {
        for word in combos {
            word.print0b8()
        }
    }
}

func possibleCombos() -> [Int8] {
    var combos = [Int8]()
    for a in [0, 1] {
        for b in [0, 1] {
            for c in [0, 1] {
                for d in [0, 1] {
                    combos.append(Int8(d | (c << 1) | (b << 2) | (a << 3)) )
                     // known total combos = 16
                    // How do I pass combos.count out to a progress bar?
                }
            }
        }
    }
    return combos
}

extension Int8 {func print0b8() {print("0b" + pad(string: String(self, radix: 2), toSize: 4))}}

func pad(string : String, toSize: Int) -> String {var padded = string;for _ in 0..<(toSize - string.count) {padded = "0" + padded};return padded}

In my actual App, i.e., not this playground, I have a ProgressBar in my storyboard and an IBoutlet declared in my view controller. I just can't find how to pass a value out of "possibleCombos()" and into the NSProgressIndicator.

What I have read suggests a delegate but reading about delegates hasn't helped. I need an example.

Thanks.

  • It appears playground doesn't do macOS. When I go to Xcode/Preferences/Components the only entries are iOS, tvOS, and watchOS. So AppKit is not recognized.

  • When I do "New" > "Playground..." in Xcode 13, I have a choice between iOS and macOS. I'm not sure how to change it after the fact, but maybe you need to make a new Playground and choose macOS?

  • Yeah you are right mikander... and I forgot I did that. So yes I used a macOS playground. Then, there is no simulator for macOS. Hmm.

Add a Comment

Accepted Reply

This now works. No more dispatch in calc.

For sure, code could be improved, but that's a way to get it working.

class viewContr: NSViewController {

    var comboCount: Int = 0 // NSKeyValueObservation?
    var when = DispatchTime.now()

    @IBOutlet weak var progressBar: NSProgressIndicator!
        
    @IBAction func calcStuff(_ sender: NSButton) {
        DispatchQueue.global(qos: .userInitiated).async {[self] in
            let combos =  possibleCombos()
//            DispatchQueue.main.asyncAfter(deadline: when) {
                for word in combos {
                    word.print0b8()
                }
//            }
        }
    }
    
    func possibleCombos() -> [Int8] {
        var combos = [Int8] ()
        var combosForDisplay = combos    // That's the way to get value both for return and for progress
        when = DispatchTime.now()
        
        for a in [0, 1] {
            for b in [0, 1] {
                for c in [0, 1] {
                    for d in [0, 1] {
                        let value = Int8(d | (c << 1) | (b << 2) | (a << 3))
                        combos.append(value )
                        DispatchQueue.global().asyncAfter(deadline: when) {[self] in
                           combosForDisplay.append(value)    // Do it again ; otherwise we capture the wrong value from combos
                           comboCount = (100 * combosForDisplay.count) / 16 // as! NSKeyValueObservation
                            print(comboCount, when)
                            DispatchQueue.main.async { [self] in
                                progressBar.doubleValue = Double(comboCount)
                            }
                            // known total combos = 16
                            // How do I pass combos.count out to a progress bar?
                        }
                        when = when + .milliseconds(300)
                    }
                }
            }
        }
        return combos
    }
    
}

extension Int8 {func print0b8() {print("0b" + pad(string: String(self, radix: 2), toSize: 4))}}

func pad(string : String, toSize: Int) -> String {var padded = string;for _ in 0..<(toSize - string.count) {padded = "0" + padded};return padded}
  • Thanks for responding Claude31. I'll need to move my "func possibleCombos()" into my ViewController as it is currently remote. Yes, I'm beginning to see. Since the progressBar must be a member of the ViewController, the "when" and "progressBar.doubleValue" assignment must be made within the class also.I thought (hoping) maybe there was a way to have remote functions "use" or "pass" messages into or out-of the ViewController.

    It will take me a while to study and assimilate this. Thanks!

Add a Comment

Replies

You have to create a MacOS playground.

This works in such a playground (note that I had to make a few corrections):

import Dispatch
import Foundation

var comboCount:NSKeyValueObservation?

class viewContr: NSViewController {
    @IBOutlet weak var progressBar: NSProgressIndicator!
//    deinit { comboCount?.invalidate() }
    
    @IBAction func calcStuff(_ sender: NSButton) {
        DispatchQueue.global(qos: .userInitiated).async {
            let combos =  possibleCombos()
            DispatchQueue.main.async {
                for word in combos {
                    word.print0b8()
                }
            }
        }
    }
}

func possibleCombos() -> [Int8] {
    var combos = [Int8] ()
    for a in [0, 1] {
        for b in [0, 1] {
            for c in [0, 1] {
                for d in [0, 1] {
                    combos.append(Int8(d | (c << 1) | (b << 2) | (a << 3)) )
                    comboCount = combos.count/16 as! NSKeyValueObservation
                    // known total combos = 16
                    // How do I pass combos.count out to a progress bar?
                }
            }
        }
    }
    return combos
}

extension Int8 {func print0b8() {print("0b" + pad(string: String(self, radix: 2), toSize: 4))}}

func pad(string : String, toSize: Int) -> String {var padded = string;for _ in 0..<(toSize - string.count) {padded = "0" + padded};return padded}

I tested the following in app

class viewContr: NSViewController {

    var comboCount: Int = 0 // NSKeyValueObservation?
    
    var when = DispatchTime.now()

    @IBOutlet weak var progressBar: NSProgressIndicator!
        //    deinit { comboCount?.invalidate() }
        
    @IBAction func calcStuff(_ sender: NSButton) {
        DispatchQueue.global(qos: .userInitiated).async {[self] in
            let combos =  possibleCombos()
            DispatchQueue.main.asyncAfter(deadline: when) {
                print(combos.count)
                for word in combos {
                    word.print0b8()
                }
            }
        }
    }
    
    func possibleCombos() -> [Int8] {
        var combos = [Int8] ()
        when = DispatchTime.now()
        for a in [0, 1] {
            for b in [0, 1] {
                for c in [0, 1] {
                    for d in [0, 1] {
                        DispatchQueue.global().asyncAfter(deadline: when) {[self] in
                            combos.append(Int8(d | (c << 1) | (b << 2) | (a << 3)) )
                            comboCount = (100 * combos.count) / 16 // as! NSKeyValueObservation
                            DispatchQueue.main.async { [self] in
                                progressBar.doubleValue = Double(comboCount)
                            }
                            // known total combos = 16
                            // How do I pass combos.count out to a progress bar?
                        }
                        when = when + .milliseconds(200)
                    }
                }
            }
        }
        return combos
    }
}

Indicator (determinate) works ok.

There is a problem to fix

                    word.print0b8()

because it has only one item when printing. Some dispatch to adjust. A solution would be to move it inside the possibleCombos()

But now, you should do it with async-await pattern.

Add a Comment

I will try this change:

        for d in [0, 1] {
                   combos.append(Int8(d | (c << 1) | (b << 2) | (a << 3)) )
                   comboCount = (100 * combos.count) / 16 // as! NSKeyValueObservation
                    DispatchQueue.global().asyncAfter(deadline: when) {[self] in

This now works. No more dispatch in calc.

For sure, code could be improved, but that's a way to get it working.

class viewContr: NSViewController {

    var comboCount: Int = 0 // NSKeyValueObservation?
    var when = DispatchTime.now()

    @IBOutlet weak var progressBar: NSProgressIndicator!
        
    @IBAction func calcStuff(_ sender: NSButton) {
        DispatchQueue.global(qos: .userInitiated).async {[self] in
            let combos =  possibleCombos()
//            DispatchQueue.main.asyncAfter(deadline: when) {
                for word in combos {
                    word.print0b8()
                }
//            }
        }
    }
    
    func possibleCombos() -> [Int8] {
        var combos = [Int8] ()
        var combosForDisplay = combos    // That's the way to get value both for return and for progress
        when = DispatchTime.now()
        
        for a in [0, 1] {
            for b in [0, 1] {
                for c in [0, 1] {
                    for d in [0, 1] {
                        let value = Int8(d | (c << 1) | (b << 2) | (a << 3))
                        combos.append(value )
                        DispatchQueue.global().asyncAfter(deadline: when) {[self] in
                           combosForDisplay.append(value)    // Do it again ; otherwise we capture the wrong value from combos
                           comboCount = (100 * combosForDisplay.count) / 16 // as! NSKeyValueObservation
                            print(comboCount, when)
                            DispatchQueue.main.async { [self] in
                                progressBar.doubleValue = Double(comboCount)
                            }
                            // known total combos = 16
                            // How do I pass combos.count out to a progress bar?
                        }
                        when = when + .milliseconds(300)
                    }
                }
            }
        }
        return combos
    }
    
}

extension Int8 {func print0b8() {print("0b" + pad(string: String(self, radix: 2), toSize: 4))}}

func pad(string : String, toSize: Int) -> String {var padded = string;for _ in 0..<(toSize - string.count) {padded = "0" + padded};return padded}
  • Thanks for responding Claude31. I'll need to move my "func possibleCombos()" into my ViewController as it is currently remote. Yes, I'm beginning to see. Since the progressBar must be a member of the ViewController, the "when" and "progressBar.doubleValue" assignment must be made within the class also.I thought (hoping) maybe there was a way to have remote functions "use" or "pass" messages into or out-of the ViewController.

    It will take me a while to study and assimilate this. Thanks!

Add a Comment

maybe there was a way to have remote functions "use" or "pass" messages into or out-of the ViewController

Yes there are several ways:

  • by delegation : that's the most appropriate
  • by notification: very easy. Just have the otherViewController subscribe to a notification and post one from ViewController. But take care if you need very short response time.
  • even with a direct reference of the other viewController inside ViewController (not the cleanest way)

Before detailing,

  • what do you mean by  ViewController as it is currently remote ?
  • which controller is on screen when you update ?

Claude31, first, thank you for the example of a progress bar with DispatchQueue. I can now advance. I have closed this thread and opened another with this delegation subject, since it is another issue deserving credit.