Re: NSTask and NSPipe
Re: NSTask and NSPipe
- Subject: Re: NSTask and NSPipe
- From: Fritz Anderson <email@hidden>
- Date: Wed, 27 Jun 2001 03:34:32 -0500
At 4:13 PM -0500 6/26/2001, Darin Duphorne wrote:
>
Can anyone provide me a simple example of how to pipe standardoutput
>
from a NSTask to a textfield on a window? I can't for the life of
>
me get it to work. Perhaps a simple example of calling ls -al and
>
displaying in a tableview or textfield?
The tricks are:
(1) The NSTextStorage class, instances of which hold the contents of
text fields, is a subclass of NSMutableAttributedString. This was
never obvious to me from my reading. Setting field contents is a
mystery if you don't know this.
(2) You really do have to capture the output asynchronously, because
the capacity of an NSPipe is finite. If the task fills the pipe
while you're waiting for it to finish, the task blocks and you wait
forever.
(3) -waitForDataInBackgroundAndNotify is not what you want to call if
-readInBackgroundAndNotify is what you mean. The former notifies you
that -readAvailable won't block, but does no actual reading. I spent
many a happy minute figuring this (well-documented) distinction out.
(4) -readInBackgroundAndNotify has to be renewed while there is still
data to be read. If your notification handler receives an empty
NSData, the task has closed its end of the pipe, and there will be no
more data. If you repost -readInBackgroundAndNotify anyway, you will
get empty NSData forever.
The following is overkill, as it manages some additional HI. It is a
controller class that manages a window (listingWindow) that contains
a button (chooseButton) and an NSTextView (listingText). The
controller is the button's target, and its action selector is
-chooseDirectory:. It keeps instance variables lsTask (an NSTask
that performs the ls -al), outputPipe (an NSPipe connecting the
task's standard output to the controller), and taskOutput (an
NSFileHandle that passes the actual data back from the task). My
reading of the documentation persuades me that I am neither to retain
nor to release taskOutput.
In practice, it seems that endMyTask: gets called before all (or any)
data reaches dataAvailable:. As endMyTask: releases the task and the
pipe, I was concerned that I was operating on deallocated objects.
It appears, however, that Cocoa itself retains the pipe, and possibly
the task, at least until the task's output is exhausted.
-- F
#import "ListerController.h"
@implementation ListerController (LCHousekeeping)
- (void) awakeFromNib
{
targetPath = nil;
}
- (void) dealloc
{
[self flushTaskStructure];
}
@end /* Housekeeping */
@implementation ListerController (LCTaskManagement)
- (void) flushTaskStructure
{
[lsTask release];
lsTask = nil;
[outputPipe release];
outputPipe = nil;
}
- (void) endMyTask: (NSNotification *) theNotice
{
[chooseButton setEnabled: YES];
[self flushTaskStructure];
}
- (void) dataAvailable: (NSNotification *) theNotice
{
NSData * theData;
theData = [[theNotice userInfo]
objectForKey: NSFileHandleNotificationDataItem];
if ([theData length] > 0) {
NSString * dataAsString;
NSAttributedString * dataAsAttString;
dataAsString = [[NSString alloc]
initWithData: theData
encoding: NSASCIIStringEncoding];
dataAsAttString = [[NSAttributedString alloc]
initWithString: dataAsString];
[[listingText textStorage]
appendAttributedString: dataAsAttString];
[dataAsAttString release];
[dataAsString release];
[taskOutput readInBackgroundAndNotify];
}
}
- (BOOL) startMyTask
{
BOOL retval = NO;
lsTask = [[NSTask alloc] init];
outputPipe = [[NSPipe alloc] init];
if (lsTask && outputPipe) {
[lsTask setLaunchPath: @"/bin/ls"];
[lsTask setArguments: [NSArray arrayWithObjects: @"-al",
targetPath,
nil]];
[[NSNotificationCenter defaultCenter]
addObserver: self selector: @selector(endMyTask:)
name: NSTaskDidTerminateNotification
object: lsTask];
taskOutput = [outputPipe fileHandleForReading];
[taskOutput readInBackgroundAndNotify];
[lsTask setStandardOutput: outputPipe];
[[NSNotificationCenter defaultCenter]
addObserver: self selector: @selector(dataAvailable:)
name: NSFileHandleReadCompletionNotification
object: taskOutput];
[lsTask launch];
retval = YES;
}
else {
[self flushTaskStructure];
}
return retval;
}
@end /* Task management */
@implementation ListerController
- (void) openPanelDidEnd: (NSOpenPanel *) sheet
returnCode: (int) returnCode
contextInfo: (void *) contextInfo
{
if (returnCode == NSOKButton) {
targetPath = [[sheet filenames] objectAtIndex: 0];
// Clear the text
[[listingText textStorage]
deleteCharactersInRange:
NSMakeRange(0, [[listingText textStorage] length])];
// Start the task.
if ([self startMyTask])
// Make sure we can't double-clutch on the listing
[chooseButton setEnabled: NO];
}
[sheet release];
}
- (IBAction) chooseDirectory: (id) sender
{
NSOpenPanel * sheet = [[NSOpenPanel openPanel] retain];
[sheet setCanChooseFiles: NO];
[sheet setCanChooseDirectories: YES];
[sheet beginSheetForDirectory: targetPath
file: nil
types: nil
modalForWindow: listingWindow
modalDelegate: self
didEndSelector: @selector(openPanelDidEnd:returnCode:contextInfo:)
contextInfo: nil];
}
@end /* HI event handling */