NSRunLoop in python

I'm trying to follow the advice in Apple's documentation for stopping an NSRunLoop:


"If you want the run loop to terminate, you shouldn't use 'run'. Instead, use one of the other run methods and also check other arbitrary conditions of your own, in a loop. A simple example would be:"

BOOL shouldKeepRunning = YES; /
NSRunLoop *theRL = [NSRunLoop currentRunLoop];

while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);


I'm having trouble translating that to python. It still runs forever. Noticeably, the process maxes out a core after the time interval is up.


from Cocoa import NSObject, NSSpeechRecognizer, NSRunLoop, NSDefaultRunLoopMode, NSDate 
shouldKeepRunning = True 
theFuture = NSDate.alloc().initWithTimeIntervalSinceNow_(10) 

class Controller (NSObject): 
     def init(self): 
          commands = [ "up", "down" ] 
          self.recognizer = NSSpeechRecognizer.alloc().init() 
          self.recognizer.setCommands_(commands) 
          self.recognizer.startListening() 
          self.recognizer.setDelegate_(self) 

     def speechRecognizer_didRecognizeCommand_(self, recognizer, command): 
          shouldKeepRunning = False 
          return command 

controller = Controller.alloc().init() 
loop = NSRunLoop.currentRunLoop() 
while shouldKeepRunning and (loop.runMode_beforeDate_(NSDefaultRunLoopMode, theFuture)): 
     pass


Also I'm not convinced I can retrieve the command from out of the class. Any thoughts? Thanks.

NSSpeechRecognizer
is scheduling the call to
speechRecognizer(_:didRecognizeCommand:)
so that it runs on the main thread but does not activate any run loop source, which is why
run(mode:before:)
is not returning. One easy fix is to force the run loop to cycle using
perform(_:with:afterDelay:)
. A delay of 0.0 is documented to run the selector on the next run loop cycle, and that gets you out of
run(mode:before:)
and allows you to notice
shouldKeepRunning
being false.

I tested this with the Swift code below (sorry it’s not Python; my days of PyObjC hacking are not officially over).

Also I'm not convinced I can retrieve the command from out of the class.

Just have your

speechRecognizer_didRecognizeCommand_
method store the command in
self.recognizedCommand
, and then you can pick it up from
controller.recognizedCommand
after the
while
is done. If you have
init
set
controller.recognizedCommand
to
None
you can tell whether something was recognised or not.

The code below shows this, albeit in Swift.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
import Cocoa

var shouldKeepRunning = true

class Controller : NSObject, NSSpeechRecognizerDelegate {

    override init() {
        self.recognizer = NSSpeechRecognizer()!
        self.recognizedCommand = nil
        super.init()
        self.recognizer.commands = [ "up", "down" ]
        self.recognizer.delegate = self
        self.recognizer.startListening()
    }

    let recognizer: NSSpeechRecognizer
    var recognizedCommand: String?

    func speechRecognizer(_ sender: NSSpeechRecognizer, didRecognizeCommand command: String) {
        assert(Thread.isMainThread)
        NSLog("recognised: %@", command)
        self.recognizedCommand = command
        shouldKeepRunning = false
        self.perform(#selector(noop), with: nil, afterDelay: 0.0)
    }

    @objc
    func noop() {
        NSLog("noop")
    }
}

func main() {
    NSLog("done")
    let controller = Controller()
    let deadline = Date(timeIntervalSinceNow: 10)
    while shouldKeepRunning && Date() < deadline {
        NSLog(">run")
        RunLoop.current.run(mode: .defaultRunLoopMode, before: deadline)
        NSLog("<run")
    }
    NSLog("done")
    NSLog("controller.recognizedCommand: %@", controller.recognizedCommand ?? "n/a")
}

main()
NSRunLoop in python
 
 
Q