Search code examples
objective-cmacosabstract-classsubclassingnstask

NSTask subclass error - launchPath only defined for abstract class


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

Solution

  • 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