1

I am trying to create a Terminal like application that can display logs when application is running.

Current implementation uses NSScrollView -> NSTextView. However, I notice that the size of the NSTextView is not large enough for my program and I can not update the UI very frequently.

So let's say we have a sample code like this below, (everything else stays the same as a brand new Xcode APplication project).

The program continues to print some garbage text to the UI every 0.1 second and update the view. I found the program crashes after about 4 minutes of running every time. Also i have to add a 0.1 second delay between each garbage text. If i didn't put any time of delay, the program crashes right away. I want to find ways to fix this.

I am not sure if NSTextView is still the good choice of my application. If not, can anyone point me to the right direction, which view or collection of views that can behave like a Terminal Application.

Thanks in advance.

#import "AppDelegate.h"

@implementation AppDelegate

@synthesize window = _window;
@synthesize queue = _queue;
@synthesize theTextView = _theTextView;

- (void)dealloc
{
    [super dealloc];
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    // Insert code here to initialize your application 322, 40, 895, 720
    NSScrollView *scrollview = [[NSScrollView alloc]
                                initWithFrame:[[_window contentView] frame]];
    NSSize contentSize = [scrollview contentSize];

    [scrollview setBorderType:NSNoBorder];
    [scrollview setHasVerticalScroller:YES];
    [scrollview setHasHorizontalScroller:NO];
    [scrollview setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];

    _theTextView = [[NSTextView alloc] initWithFrame:NSMakeRect(0, 0,
                                                               contentSize.width, contentSize.height)];
    [_theTextView setMinSize:NSMakeSize(0.0, contentSize.height)];
    [_theTextView setMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
    [_theTextView setVerticallyResizable:YES];
    [_theTextView setHorizontallyResizable:NO];
    [_theTextView setAutoresizingMask:NSViewWidthSizable];

    [[_theTextView textContainer] setContainerSize:NSMakeSize(contentSize.width, FLT_MAX)];
    [[_theTextView textContainer] setWidthTracksTextView:YES];

    [scrollview setDocumentView:_theTextView];
    [_window setContentView:scrollview];
    [_window makeKeyAndOrderFront:nil];
    [_window makeFirstResponder:_theTextView];

    [[_theTextView enclosingScrollView] setHasHorizontalScroller:YES];
    [_theTextView setHorizontallyResizable:YES];
    [_theTextView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
    [[_theTextView textContainer] setContainerSize:NSMakeSize(FLT_MAX, FLT_MAX)];
    [[_theTextView textContainer] setWidthTracksTextView:NO];
    _queue = dispatch_queue_create("com.example.MyQueue", NULL);

    [self start];
}

- (void) start {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
        [self feed];
    });

}
- (void) feed {
    while (1) {
        dispatch_async(_queue, ^(void){
            NSString * displaytext = (NSString *) CFSTR("Testing Line - asdfasdfklqjwer;opfjiasdlk;fjasd\n");

            NSAttributedString *string = [[NSAttributedString alloc] initWithString:displaytext];
            NSLog(@"Output is %@", string);

            //        [[_theTextView textStorage] appendAttributedString:string];

            [[_theTextView textStorage] beginEditing];
            [[_theTextView textStorage] appendAttributedString:string];
            [[_theTextView textStorage] endEditing];
            [_theTextView scrollRangeToVisible:NSMakeRange([[_theTextView string] length], 0)];

            [string release];

        });
        usleep(100000);
    }

}

@end
Negative Zero
  • 1,224
  • 3
  • 10
  • 19

1 Answers1

3

AppKit is not, in general, thread safe. You're changing the text storage, and scrolling the text view, from a background thread -- so it's no surprise that it works inconsistently or crashes.

All you've demonstrated is that you're using AppKit incorrectly, not that NSTextView is fatally flawed for your purposes. (It might still be fatally flawed, but this isn't a good reason.)

To do this test correctly:

- (void)start
{
    [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(feedOne:) userInfo:nil repeats:YES];
}

- (void)feedOne:(NSTimer*)timer
{
    NSString* displaytext = @"Testing Line - asdfasdfklqjwer;opfjiasdlk;fjasd\n";    
    NSAttributedString* string = [[NSAttributedString alloc] initWithString:displaytext];

    [[_theTextView textStorage] beginEditing];
    [[_theTextView textStorage] appendAttributedString:string];
    [[_theTextView textStorage] endEditing];
    [_theTextView scrollRangeToVisible:NSMakeRange([[_theTextView string] length], 0)];

    [string release];
}
Kurt Revis
  • 27,695
  • 5
  • 68
  • 74
  • I actually thought about multithreading in the original code. That's why I added a serial queue when using dispatch_async in the function feed. I would assume this serial queue would get processed one block by one block? I am still new to objc and GCD. But isn't the original code actually running in serial other than parallel. I am little bit confused. – Negative Zero Mar 20 '12 at 01:17
  • Your code was running on *a* serial queue, but it wasn't *the same* queue that AppKit uses. You would use `dispatch_async(dispatch_get_main_queue(), ...)` for that. – Kurt Revis Mar 20 '12 at 01:23