8
votes

I am very new to Cocoa and this is probably a complete newb question. I am frustrated to death however.

I have an extremely simple Cocoa app (called "lines") to test sending 1000 lines of text to a text view.

I started in Xcode 4 with "new Cocoa project" with all the defaults. This gives a blank window object upon which I can drag IB UI elements.

The UI I then constructed consists of a Text View and a button on the window of a NIB file. I am using Xcode 4 to drag those two elements to the .h file. The text view is connected to outView and the "1000" button is connected to one_thousand_button method.

The UI

Clicking the button "1000" triggers a loop to print 1,000 lines of text ("line 1\n" "line 2\n" ... "line 1000") to the NSTextView called "outView"

Here is the entire code (other than the .XIB file described):

linesAppDelegate.h:

#import <Cocoa/Cocoa.h>

@interface linesAppDelegate : NSObject <NSApplicationDelegate> {
@private
    NSWindow *window;
    NSTextView *outView;
}

@property (assign) IBOutlet NSWindow *window;
@property (assign) IBOutlet NSTextView *outView;
- (IBAction)one_thousand_button:(id)sender;

@end

linesAppDelegate.m:

#import "linesAppDelegate.h"

@implementation linesAppDelegate

@synthesize window;
@synthesize outView;

- (IBAction)one_thousand_button:(id)sender {
    NSString* oldString;
    NSString* newString;

    for(int i=1; i<=1000; i++){
        oldString = [outView string];
        newString = [oldString stringByAppendingString: 
                     [NSString stringWithFormat: @"Line %i\n",i]];
        [outView setString: newString];    
    }
}
@end

This is REALLY SLOW to execute. Perhaps 7 seconds the first time and increasingly slow with each press of "1000". Even has the spinning colorful pizza of death!

I realize that this is probably not the right way to fill a NSTextView with 1,000 lines of text and that the loop that read the contents of the text view and appends that with stringByAppendingString method is the bottleneck.

What is the alternative method however?

Result

I wrapped this code:

    mach_timebase_info_data_t info;
    mach_timebase_info(&info);
    uint64_t start = mach_absolute_time();

// timed code    

    uint64_t duration = mach_absolute_time() - start;
    duration *= info.numer;
    duration /= info.denom;
    duration /= 1000000;

    NSLog(@"timed code took %lld milliseconds!", duration);

around the code from Adam Preble, my original, and drewk:

                 Adam Preble  (Adam, base)     drewk    my pitiful code
 1st run:           3ms          269ms         260ms      1,950ms
 2nd run            3ms          269ms         250ms      2,332ms
 3rd run:           2ms          270ms         260ms      2,880ms

The first run adds 1,000 lines; 2nd run adds another 1,000 lines, etc. (Adam, base) is his code without the beginEditing and endEditing

It is clear that using beginEditing and endEditing is WAY faster!

See the Apple documents on Synchronizing Editing.

2
The slowness probably isn't the NSTextView itself but rather that you're allocating 2 new strings every time you loop (1 via the stringWithFormat: and the other as the result of stringByAppendingString:) and then changing the string of the textView every iteration. @drewk's answer should really help to speed this up.Dave DeLong
"spinning colorful pizza of death!" 😂😂😂TheNextman

2 Answers

17
votes

Wrap your updates to the text storage in calls to beginEditing and endEditing. This causes Cocoa to hold all of its change notifications until you have finished making your changes to the text storage.

- (IBAction)oneThousandButton:(id)sender
{
    NSTextStorage *textStorage = [outView textStorage];
    [textStorage beginEditing];
    for (int i = 1; i <= 1000; i++)
    {
        NSString *line = [NSString stringWithFormat: @"Line %i\n", i];
        [textStorage replaceCharactersInRange:NSMakeRange([textStorage length], 0)
                                   withString:line];
    }
    [textStorage endEditing];
}

On my system the above action runs in about 10ms. If I comment out the calls to beginEditing/endEditing, it takes about 470ms.

2
votes

Try this:

- (IBAction)one_thousand_button:(id)sender {

    for(int i=1; i<=1000; i++){      
         [[[outView textStorage] mutableString] appendString: 
                    [NSString stringWithFormat: @"Line %i\n",i]];
    }
}

There is an invaluable "quickie" reference to Objective-C HERE with NSTextView covered HERE.

Best...