I'm trying to create a subclass of NSTask
, that is augmented with a name
and an activityDescription
property, for the purpose of displaying status, in a UI in an OSX desktop application.
However, when I try to set the launchPath
or arguments
property on my subclass instance, I get these errors:
launchPath only defined for abstract class. Define -[Task launchPath]!
arguments only defined for abstract class. Define -[Task arguments]!
So, I defined setLaunchPath:
and setArguments:
as shown below, and I still get the same errors.
By the way, NSData stringValue is defined in NSData+Additions.h:
- (NSString *) stringValue {
return [[NSString alloc] initWithData:self encoding:NSUTF8StringEncoding];
}
Any help is greatly appreciated!
Task.h
#import <Foundation/Foundation.h>
@protocol TaskDelegate;
@interface Task : NSTask {
NSString *_launchTask;
NSArray *_arguments;
}
@property (weak, nonatomic) id<TaskDelegate> delegate;
@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSString *activityDescription;
//To be implemented by subclasses, should be called just before launch
- (void) setupTask;
- (BOOL) isConfigured;
//Launches the task - includes any setup required before the task is launched
- (void) launchAndWait;
- (void) setLaunchPath:(NSString *)launchPath;
- (void) setArguments:(NSArray *)arguments;
@end
@protocol TaskDelegate <NSObject>
- (void) task:(Task *)task didReceiveTaskError:(NSString *)errorString;
@end
Task.m
#import "Task.h"
#import "NSData+Additions.h"
@interface Task ()
@end
@implementation Task
- (id) init
{
self = [super init];
if (self) {
}
return self;
}
- (void) errorOccurred:(NSNotification *)notification {
if (_delegate) {
NSData *readData = [[notification userInfo] objectForKey:NSFileHandleNotificationDataItem];
NSString *outputString = [readData stringValue];
if (outputString.length)
[_delegate task:self didReceiveTaskError:outputString];
}
}
- (void) setupTask
{
//To be implemented by subclasses
}
- (BOOL) isConfigured
{
return (self.launchPath != nil);
}
//Process task conguration and execution is optional - if process task is not configured, this returns immediately
- (void) launchAndWait
{
//Allow setupTask to be used, without actually launching
if (![self isConfigured])
return;
NSPipe *errorPipe = [NSPipe pipe];
self.standardError = errorPipe;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(errorOccurred:)
name:NSFileHandleReadToEndOfFileCompletionNotification
object:[errorPipe fileHandleForReading]];
[[errorPipe fileHandleForReading] readToEndOfFileInBackgroundAndNotify];
[self launch];
[self waitUntilExit];
//Tear down
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void) setLaunchPath:(NSString *)launchPath
{
_launchTask = launchPath;
}
- (void) setArguments:(NSArray *)arguments
{
_arguments = arguments;
}
@end
I decided to use the composition approach and use NSTask
inside my Task
class, instead of inheritance, and it's working well.
Here is the updated class:
Task.h
#import <Foundation/Foundation.h>
@protocol TaskDelegate;
@interface Task : NSObject {
__weak id<TaskDelegate> _delegate;
NSTask *_processTask;
}
@property (weak, nonatomic) id<TaskDelegate> delegate;
@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSString *activityDescription;
//To be called just before launch
- (void) setupTask;
- (BOOL) processTaskConfigured;
//Launches the task - includes any setup required before the task is launched
- (void) launchAndWait;
//////////////////////////////////////////////////////////////////////////////////////////////// Process task delegation
@property (readonly) int terminationStatus;
- (BOOL) isRunning;
- (void) terminate;
@end
@protocol TaskDelegate <NSObject>
- (void) task:(Task *)task didReceiveTaskError:(NSString *)errorString;
@end
Task.m
#import "Task.h"
#import "NSData+Additions.h"
@interface Task ()
//Delegating to NSTask
@property (strong, nonatomic) NSTask *processTask;
@end
@implementation Task
- (id) init
{
self = [super init];
if (self) {
self.processTask = [NSTask new];
}
return self;
}
- (void) errorOccurred:(NSNotification *)notification {
if (_delegate) {
NSData *readData = [[notification userInfo] objectForKey:NSFileHandleNotificationDataItem];
NSString *outputString = [readData stringValue];
if (outputString.length)
[_delegate task:self didReceiveTaskError:outputString];
}
}
- (void) setupTask
{
//To be implemented by subclasses
}
- (BOOL) processTaskConfigured
{
return (_processTask.launchPath != nil);
}
//Process task conguration and execution is optional - if process task is not configured, this returns immediately
- (void) launchAndWait
{
if (!_processTask.launchPath)
return;
NSPipe *errorPipe = [NSPipe pipe];
_processTask.standardError = errorPipe;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(errorOccurred:)
name:NSFileHandleReadToEndOfFileCompletionNotification
object:[errorPipe fileHandleForReading]];
[[errorPipe fileHandleForReading] readToEndOfFileInBackgroundAndNotify];
[_processTask launch];
[_processTask waitUntilExit];
//Tear down
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
////////////////////////////////////////////////////////////////////////////////////////// Process task delegation
- (int) terminationStatus
{
if ([self processTaskConfigured]) {
return [_processTask terminationStatus];
}
return 0;
}
- (BOOL) isRunning
{
return [_processTask isRunning];
}
- (void) terminate
{
if ([self processTaskConfigured]) {
[_processTask terminate];
}
}
@end