NSTask for cocoa methods, not shell scripts

I’m using NSTask quite effectively for bash commands in my application.

However, I’d like to use it for doing a few commands which aren’t shell commands.

One command in particular is :

currentInterface’s associateToEnterpriseNetwork_identity_username_password_error_(myNetwork, missing value, myUser, myUserPW, missing value)

This connects to an enterprise wifi network.

Problem is, if the user has typed in the wrong username and password, it takes three minutes before it times out. Not good. During this time, the interface is completely unresponsive

What I would like to do is to kick off this command but instantly regain control.

I’ve got stefan’s book in front of me, specifically page 210, but it concentrates on shell commands not regular instructions. Any ideas?

Hello.

You had me there, for a couple of secs. :wink:

You can use the performSelectorInBackground_withObject_(“nameOfHandler”, theObject) to run an method in the background.

You probably mean shane’s :wink:

Unfortunately that doesn’t solve the problem by itself. The method will be called on a different thread, but the application’s single instance of AppleScript will be blocked until it returns. This is one of the areas where ASObjC falls short.

Probably the only solution is to use an Objective-C helper class a and put the call in a method there. Then performSelectorInBackground_withObject_ should solve the problem.

And Rob: NSTask is for running processes, not methods.

– Not Stefan, but after two days’ flying to get home, I had to check…

Firstly, apologies on the naming issue :slight_smile: It had been a long week!

I’d been thinking about this all weekend, and couldn’t wait to get to work to try out my thoughts:

My thinking was this:

set theTimer to current application's NSTimer's scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(15, me, "timerFired:", "Whatever", true)

as per Shane’s book! :slight_smile:

However, I do recall ASOC being single instance :frowning:

So, Shane… If you have a “hello world” type example of this or ideas on how I could call the following in Objective-C helper class:


current application's NSThread's sleepForTimeInterval_(15)
set currentInterface to CWInterface's interface()
currentInterface's setPower_error_(FALSE, missing value)
current application's NSThread's sleepForTimeInterval_(5)
currentInterface's setPower_error_(TRUE, missing value)

In a nutshell:

With the wrong credentials, currentInterface’s associateToEnterpriseNetwork_identity_username_password_error_(myNetwork, missing value, myUser, myUserPW, missing value) hogs the single instance of the ASOC until it times out (which is around 3 minutes)

I’d like to kick off the code above, but at the same time, connect to my wifi network

I’m very much appreciative of the help that’s already been given with my various wifi issues. This should be the last issue :slight_smile:

Sorry for beeing so unclear and mixing up names, I actually meant to call detachNewThreadSelector_toTarget_withObject_ of NSThread class which is the same as performSelectorInBackground method from another object than self. Of course the target object has to be an objective-C object/class. This way you don’t need to create an Objective-C helper class.

p.s. how was the Netherlands?

Edit: with some example code:
The Objective-C class to test with named sleeper (just to simulate a long command). I’m using class methods because there are no instance variables needed in this method. So we don’t need to allocate this object later in ASObjC.

[code]#import <Cocoa/Cocoa.h>

@interface sleeper : NSObject {}

  • (void) startSleeping:(NSUInteger) microseconds;
    @end

@implementation sleeper

  • (void) startSleeping:(NSUInteger) microseconds
    {
    NSLog(@“Before Sleep”);
    usleep(microseconds);
    NSLog(@“After Sleep”);
    return;
    }
    @end[/code]
    Here’s my application delegate:
property sleeper : class "sleeper"
property NSThread : class "NSThread"

script PerformSelectorInBackgroundAppDelegate
    property parent : class "NSObject"
    property startSleepBtn : missing value --connects to a button in the window
    
    on startSleep_(sender)
        log "Start Thread"
        set myThread to NSThread's detachNewThreadSelector_toTarget_withObject_("startSleeping:", sleeper, 500000)
        delay 2.5
        log "Continue"
    end startSleep_
    
    on applicationWillFinishLaunching_(aNotification)
        -- Insert code here to initialize your application before any files are opened
    end applicationWillFinishLaunching_
    
    on applicationShouldTerminate_(sender)
        -- Insert code here to do any housekeeping before your application quits 
        return current application's NSTerminateNow
    end applicationShouldTerminate_
end script

In my log it will appear nicely, The first 2.5 seconds my button hangs, the next 2.5 seconds it will not while the thread is still running as you will see in your log view. My results are as expected:

Start Thread Before Sleep Continue After Sleep

You may still be able to use performSelectorInBackground_withObject_ if the selector is in an Objective-C superclass of the AS class – the key thing is that the selector must be Cocoa/Objective-C, not ASObjC, so that whatever happens can’t block the single instance of AppleScript.

Whatever, I think the OP was originally after an Obj-C class containing a method like this (untested):

+(void)doStuffWith:(NSDictionary *)dict
{
// get currentInterface however that's done, then...
BOOL result = [currentInterface associateToEnterpriseNetwork:[dict valueForKey:@"myNetwork"] identity:nil username[dict valueForKey:@"userName"] password[dict valueForKey:@"userPassword"] error:NULL];
[[dict valueForKey:@"sendingObject"] methodSucceeded:[NSNumber numberWithBool:result]];
}

In the case of a superclass, you’d probably make it an instance method. The withObject_ parameter would be an AS record like {myNetwork:whatever, userName:“Fred”, userPassword:“1234”, sendingObject:me}, and it would have a handler methodSucceeded_(successFlag) that would be called with the result.

At least, I think that would work…

The whole trip was wonderful, if exhausting, thanks. Lots of walking; my OV-chipkaart barely got used…

Well I think we’re on the same wavelength but I was thinking to send associateToEnterpriseNetwork call to the thread directly (I know the multiple parameter issue, but there is an workaround for that: NSInvocation).

Nice to hear!

Thanks to all that have posted so far.

So, I’m guessing that I create a dictionary object first in ASOC, such as :

tell current application's NSDictionary to set myDict to dictionaryWithObjects_forKeys_({WiFiInterface:currentInterface, ssid:myNetwork, identity:missing value, userName:MyUser, userPassword:MyUserPW, errormsg:missing value})

Then, we call the Cocoa object (that bit is a little fuzzy). Something along the lines of:

set myThread to NSThread's detachNewThreadSelector_toTarget_withObject_("connectToWifi:", myDict)

and in the C classes, we have

+(void)doStuffWith:(NSDictionary *)dict
{
// get currentInterface however that's done, then...
BOOL result = [currentInterface associateToEnterpriseNetwork:[dict valueForKey:@"myNetwork"] identity:nil username[dict valueForKey:@"userName"] password[dict valueForKey:@"userPassword"] error:NULL];
[[dict valueForKey:@"sendingObject"] methodSucceeded:[NSNumber numberWithBool:result]];
}

I know that under the +(void)doStuffWith:(NSDictionary *)dict I need to declare the items

Am I on the right track?

With regards to what goes where: I’d much rather have the associateToEnterpriseNetwork within the C class. This allows the UI to be responsive (indeterminate progress bars. etc)

As you’re passing it as an argument, you can just use the AS record.

Your first argument should match the method name, so “doStuffWith:” in this case, and you left out a target, which in this case will be “current application’s ”.

Looks like it to me.

So close!!! But annoyingly not working. My code so far:

connectWiFi.h

#import <Foundation/Foundation.h>
@interface connectWiFi : NSObject
@end

connectWiFi.m

#import <CoreWLAN/CoreWLAN.h>

@interface connectWiFi : NSObject {}
@end

@implementation connectWiFi
+(void)connectToWiFi:(NSDictionary *)dict
{
    NSLog(@"with cocoa");
    // get currentInterface however that's done, then...
    BOOL result = [[dict valueForKey:@"currentInterface"] associateToEnterpriseNetwork:[dict valueForKey:@"myNetwork"] identity:nil username:[dict valueForKey:@"userName"] password:[dict valueForKey:@"userPassword"] error:NULL];
    // [[dict valueForKey:@"sendingObject"] methodSucceeded:[NSNumber numberWithBool:result]];
}
@end

definitions at the top of my app delegate:

property connectWiFi : class "connectWiFi"
property NSThread : class "NSThread"

script AppDelegate
	property parent : class "NSObject"
	property CWInterface : class "CWInterface"

my ASOC code

set WiFiNetwork to "bobsnetwork"
set CECUser to "bob"
set CECUserPW to "bobspassword"

set currentInterface to CWInterface's interface()
set networks to currentInterface's scanForNetworksWithName_error_(WiFiNetwork, missing value)
set myNetwork to networks's anyObject()
log "before dict worked"
tell current application's NSDictionary to set myDict to dictionaryWithObjects_forKeys_({currentInterface, myNetwork, CECUser, CECUserPW})
log "dict worked"
set myThread to NSThread's detachNewThreadSelector_toTarget_withObject_("current application's connectWifi:", myDict)

Issues:

  1. // get currentInterface however that’s done, then…
    BOOL result = [[dict valueForKey:@“currentInterface”] associateToEnterpriseNetwork:[dict valueForKey:@“myNetwork”] identity:nil username:[dict valueForKey:@“userName”] password:[dict valueForKey:@“userPassword”] error:NULL]; has a small issue with “unused variable ‘result’” I’m guessing that’s more for indication rather than an error

  2. It builds (which was a shock to me!)

  3. When running, I get the following issue:
    2013-05-14 11:18:27.449 SetupBob[68100:303] before dict worked This isn’t an issue, just logging
    2013-05-14 11:18:27.451 SetupBob[68100:303] CMSAJourney:User selected Wi-Fi: encountered an error: I have this in a try and capture any errors method dictionaryWithObjects:forKeys: expected at least 2 arguments, got 1.

The other thing I’m confused with is this:

@implementation connectWiFi
+(void)connectToWiFi:(NSDictionary *)dict

and

set myThread to NSThread’s detachNewThreadSelector_toTarget_withObject_(“current application’s connectWifi:”, myDict)

We don’t seem to use connectToWiFi anywhere. is this superfluous? Should it be renamed?

I know there’s a lot above… The good thing is that I seem to be moving closer and closer to doing everything in native cocoa!

That should have three arguments, something like:

set myThread to NSThread's detachNewThreadSelector_toTarget_withObject_("connectToWiFi:", current application's connectWifi, myDict)

Just use a record:

set myDict to {currentInterface:currentInterface, myNetwork:myNetwork, userName:CECUser, userPassword:CECUserPW}

Solved the first error with

NSLog(@"result is %d", result);

Working perfectly!!!

Many thanks :slight_smile:

Takes 90 seconds for the cocoa class to log a result, but we’ve already got past that by advising the user, etc, and they’ve then typed in new credentials

Again, I can’t offer enough thanks for the help on here :slight_smile: