Real Time Nstask Output to Nstextview With Swift

Real time NSTask output to NSTextView with Swift

(See Patrick F.'s answer for an update to Swift 3/4.)

You can read asynchronously from a pipe, using notifications.
Here is a simple example demonstrating how it works, hopefully that
helps you to get started:

let task = NSTask()
task.launchPath = "/bin/sh"
task.arguments = ["-c", "echo 1 ; sleep 1 ; echo 2 ; sleep 1 ; echo 3 ; sleep 1 ; echo 4"]

let pipe = NSPipe()
task.standardOutput = pipe
let outHandle = pipe.fileHandleForReading
outHandle.waitForDataInBackgroundAndNotify()

var obs1 : NSObjectProtocol!
obs1 = NSNotificationCenter.defaultCenter().addObserverForName(NSFileHandleDataAvailableNotification,
object: outHandle, queue: nil) { notification -> Void in
let data = outHandle.availableData
if data.length > 0 {
if let str = NSString(data: data, encoding: NSUTF8StringEncoding) {
print("got output: \(str)")
}
outHandle.waitForDataInBackgroundAndNotify()
} else {
print("EOF on stdout from process")
NSNotificationCenter.defaultCenter().removeObserver(obs1)
}
}

var obs2 : NSObjectProtocol!
obs2 = NSNotificationCenter.defaultCenter().addObserverForName(NSTaskDidTerminateNotification,
object: task, queue: nil) { notification -> Void in
print("terminated")
NSNotificationCenter.defaultCenter().removeObserver(obs2)
}

task.launch()

Instead of print("got output: \(str)") you can append the received
string to your text view.

The above code assumes that a runloop is active (which is the case
in a default Cocoa application).

Show console output in NSScrollView and NSTextView in Swift

If assigning a closure (e.g. code block) would work at all, you'd have to put it after an '=' in the declaration, not after the : on the LHS. You're basically trying to declare the type as a static block of code. I'm surprised the compiler it lets you get away with that all complaining only about the IBOutlet aspect itself.

cURL through NSTask not terminating if a pipe is present

Expanding a little on @Hod's answer: The standard output of the launched
process is redirected to a pipe, but your program never reads from the
other pipe end. A pipe has a limited buffer, see for example
How big is the pipe buffer?
which explains that the pipe buffer size on macOS is (at most) 64KB.

If the pipe buffer is full then the launched process cannot write on it
anymore. If the process uses blocking I/O then a write() to the pipe will block until until at least one byte can be written. That does
never happen in your case, so the process hangs and does not terminate.

The problem can occur only if the amount written to standard output
exceeds the pipe buffer size, which explains why it happens only with certain URLs and not with others.

As a solution, you can read from the pipe, e.g. with

let data = pipe.fileHandleForReading.readDataToEndOfFile()

before waiting for the process to terminate. Another option is to
use asynchronous reading, e.g. with the code from Real time NSTask output to NSTextView with Swift:

pipe.fileHandleForReading.readabilityHandler = { fh in
let data = fh.availableData
// process data ...
}

That would also allow to read both standard output and standard error
from a process via pipes without blocking.

NSTask's real-time output

You can use NSFileHandle's waitForDataInBackgroundAndNotify method to receive a notification when the script writes data to its output. This will only work, however, if the interpreter sends the strings immediately. If it buffers output, you will get a single notification after the task exits.

- (void)awakeFromNib {
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath: @"/usr/bin/php"];

NSArray *arguments;
arguments = [NSArray arrayWithObjects: @"-r", @"echo \"first\n\"; sleep(1); echo \"second\n\"; sleep(1); echo \"third\n\";", nil];
[task setArguments: arguments];

NSPipe *p = [NSPipe pipe];
[task setStandardOutput:p];
NSFileHandle *fh = [p fileHandleForReading];
[fh waitForDataInBackgroundAndNotify];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receivedData:) name:NSFileHandleDataAvailableNotification object:fh];

[task launch];

}

- (void)receivedData:(NSNotification *)notif {
NSFileHandle *fh = [notif object];
NSData *data = [fh availableData];
if (data.length > 0) { // if data is found, re-register for more data (and print)
[fh waitForDataInBackgroundAndNotify];
NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@" ,str);
}
}

How to redirect stdout to NSScrollView in Swift 4.0 Cocoa Application?

You need a Pipe and a FileHandle

let path = "/bin/ls"
let arguments = ["-la"]
// sender.isEnabled = false
let task = Process()
task.launchPath = path
task.arguments = arguments
let outputPipe = Pipe()
task.standardOutput = outputPipe
task.launch()
task.waitUntilExit()
let data = (task.standardOutput as! Pipe).fileHandleForReading.readDataToEndOfFile()
let output = String(data:data, encoding: .utf8)!
print(output)
// sender.isEnabled = true

And please conform to the naming convention that variable and function / method names start with a lowercase letter.

NSTask real-time monitoring output

-(void)uploadData
{
setenv([@"PASSWORD" UTF8String], [mPassword UTF8String], 1);
[task setLaunchPath:executablePathRoot];
[task setArguments:array];
NSPipe *pipe = [NSPipe pipe];
NSPipe *errorPipe = [NSPipe pipe];
[task setStandardOutput:pipe];
[task setStandardError:errorPipe];
//keeps your log where it belongs
//[task setStandardInput:[NSPipe pipe]];

NSFileHandle *outFile = [pipe fileHandleForReading];
NSFileHandle *errFile = [errorPipe fileHandleForReading];


[task launch];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(terminated:)
name:NSTaskDidTerminateNotification
object:task];

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(outData:)
name:NSFileHandleDataAvailableNotification
object:outFile];

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(errData:)
name:NSFileHandleDataAvailableNotification
object:errFile];


[outFile waitForDataInBackgroundAndNotify];
[errFile waitForDataInBackgroundAndNotify];
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
while(!terminated)
{
if (![[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]])
{
break;
}
[pool release];
pool = [[NSAutoreleasePool alloc] init];
}
[pool release];

[self appendDataFrom:outFile to:output];
[self appendDataFrom:errFile to:error];
//[task waitUntilExit];
[task release];
}


-(void) outData: (NSNotification *) notification
{
NSLog(@"outData");
NSFileHandle *fileHandle = (NSFileHandle*) [notification object];
[self appendDataFrom:fileHandle to:output];
[fileHandle waitForDataInBackgroundAndNotify]; //Checks to see if data is available in a background thread.
}


-(void) errData: (NSNotification *) notification
{
NSLog(@"errData");
NSFileHandle *fileHandle = (NSFileHandle*) [notification object];
[self appendDataFrom:fileHandle to:output];
[fileHandle waitForDataInBackgroundAndNotify];
}

- (void) terminated: (NSNotification *)notification
{
NSLog(@"Task terminated");
[[NSNotificationCenter defaultCenter] removeObserver:self];
terminated =YES;
}


Related Topics



Leave a reply



Submit