Simple progress bar is not updating - objective-c

I am implementing a Cocoa Application which is just a simple progress bar that starts when I press a button.
The situation is: I can see Animation is Start and Stop when I press the button, but the progress bar will not update the value.
I had also tried the solution mentioned here but it doesn't work:
How do I update a progress bar in Cocoa during a long running loop?
Can someone help to see where is the problem in my source code?
Here is my source.
SimpleProgressBar.m
#import "SimpleProgressBar.h"
#implementation SimpleProgressBar
#synthesize progressBar;
int flag=0;
-(IBAction)startProgressBar:(id)sender{
if(flag ==0){
[self.progressBar startAnimation:sender];
flag=1;
}else{
[self.progressBar stopAnimation:sender];
flag=0;
}
[self.progressBar displayIfNeeded];
[self.progressBar setDoubleValue:10.0];
int i=0;
for(i=0;i<100;i++){
NSLog(#"progr: %f",(double)i);
[self.progressBar setDoubleValue:(double)i];
[self.progressBar setNeedsDisplay:YES];
}
}
#end
SimpleProgressBar.h
#import < Foundation/Foundation.h >
#interface SimpleProgressBar : NSObject{
__weak NSProgressIndicator *progressBar;
}
#property (weak) IBOutlet NSProgressIndicator *progressBar;
-(IBAction)startProgressBar:(id)sender;
#end
Thank you very much for any helpful answer.
Update:
Here is my porting from the solution and it doesn't work:
SimpleProgressBar.m
#import "SimpleProgressBar.h"
#implementation SimpleProgressBar
#synthesize progressBar;
int flag=0;
-(IBAction)startProgressBar:(id)sender{
if(flag ==0){
[self.progressBar startAnimation:sender];
flag=1;
}else{
[self.progressBar stopAnimation:sender];
flag=0;
}
[self.progressBar displayIfNeeded];
[self.progressBar setDoubleValue:0.0];
void(^progressBlock)(void);
progressBlock = ^{
[self.progressBar setDoubleValue:0.0];
int i=0;
for(i=0;i<100;i++){
//double progr = (double) i / (double)100.0;
double progr = (double) i;
NSLog(#"progr: %f",progr);
dispatch_async(dispatch_get_main_queue(),^{
[self.progressBar setDoubleValue:progr];
[self.progressBar setNeedsDisplay:YES];
});
}
};
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue,progressBlock);
}

Update:
A couple of observations:
It strikes me that if you want to watch the NSProgressIndicator advance, you need to add a sleepForTimeInterval or else the for loop iterates so quickly that you won't see the progress indicator advance, but rather you'll just see it quickly end up in its final state. If you insert sleepForTimeInterval, you should see it progress:
self.progressIndicator.minValue = 0.0;
self.progressIndicator.maxValue = 5.0;
[self.progressIndicator setIndeterminate:NO];
self.progressIndicator.doubleValue = 0.001; // if you want to see it animate the first iteration, you need to start it at some small, non-zero value
for (NSInteger i = 1; i <= self.progressIndicator.maxValue; i++)
{
[NSThread sleepForTimeInterval:1.0];
[self.progressIndicator setDoubleValue:(double)i];
[self.progressIndicator displayIfNeeded];
}
Or, if you wanted to do the for loop on a background thread, and dispatch the updates back to the main queue:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (NSInteger i = 1; i <= self.progressIndicator.maxValue; i++)
{
[NSThread sleepForTimeInterval:1.0];
dispatch_async(dispatch_get_main_queue(), ^{
[self.progressIndicator setDoubleValue:(double)i];
[self.progressIndicator displayIfNeeded];
});
}
});
You are using startAnimation and stopAnimation, but according to the documentation each of these "does nothing for a determinate progress indicator," so these calls seem inappropriate for this situation.
My original answer, below, was predicated on the comment in the Threads and Your User Interface in the Threading Programming Guide, which says:
If your application has a graphical user interface, it is recommended that you receive user-related events and initiate interface updates from your application’s main thread. This approach helps avoid synchronization issues associated with handling user events and drawing window content. Some frameworks, such as Cocoa, generally require this behavior, but even for those that do not, keeping this behavior on the main thread has the advantage of simplifying the logic for managing your user interface.
But the answer below is (incorrectly) an iOS answer, so is not applicable.
Original answer:
Your for loop is running on the main thread, and thus UI updates won't appear until you yield back to the runloop. You're also going through that loop so quickly that even if you properly dispatched that to a background queue, you wouldn't experience the progress view changing as you iterate through your loop.
So, perform the loop on a secondary thread (e.g. via GCD or operation queue) and then dispatch UI updates back to the main thread, which is now free to do UI updates. So, using your theoretical example, you could do something like:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 100; i++)
{
[NSThread sleepForTimeInterval:0.1];
dispatch_async(dispatch_get_main_queue(), ^{
[self.progressView setProgress: (CGFloat) (i + 1.0) / 100.0 animated:YES];
});
}
});
Note, having a loop that updates the progress view only makes sense if you're doing something slow enough for you to see the progress view change. In your original example, you're just looping from 0 to 99, updating the progress view. But that happens so quickly, that there's no point in a progress view in that case. That's why my above example not only employs a background queue for the loop, but also added a slight delay (via sleepForTimeInterval).
Let's consider a more realistic application of the progress view. For example, let's say I had an array, urls, of NSURL objects that represent items to be downloaded from the server. Then I might do something like:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < [urls count]; i++)
{
// perform synchronous network request (on main queue, you should only do asynchronous network requests, but on background queue, synchronous is fine, and in this case, needed)
NSError *error = nil;
NSURLResponse *response = nil;
NSURLRequest *request = [NSURLRequest requestWithURL:urls[i]];
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:response error:error];
// got it; now update my model and UI on the basis of what I just downloaded
dispatch_async(dispatch_get_main_queue(), ^{
[self.progressView setProgress: (CGFloat) (i + 1.0) / [array count] animated:YES];
// do additional UI/model updates here
});
}
});

Related

Allow user input during loop execution for macOS

I have an Objective C MacOS project In Xcode 12.3 with a loop containing code that writes to user interface controls and may display alerts. When the loop runs, the cursor becomes a rotating rainbow disc. Clicking on a toolbar item (or any user interface control) has no effect until the loop has terminated.
I would like to have a toolbar item accept user clicks during loop execution. Whilst running the loop in a separate thread would allow this, substantial recoding would be required to remove the interface references and alerts from the loop code.
Is there a way of pausing the loop execution to check for input from user controls such as toolbar items? Adding [[NSRunloop mainRunLoop] runUntilDate:[NSDate datewithTimeIntervalSinceNow:0.5]];at the start of the loop code does not achieve this.
I've tried running the loop code (runBatch) in a separate thread using
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
[self runBatch];
dispatch_sync(dispatch_get_main_queue(), ^{
});
});
The loop code is contained in runBatch, which sets and reads various UI controls and these are are flagged as only being accessible from the main thread at run time. The project builds OK. Placing these UI interactions on the main thread after async queue completion would be difficult.
An example of code showing the problem is below. The project consists of a window with an NSTextField (outlet textData) and three buttons, two of which run a loop and the third (Stop) sets a stop flag. The runMain shows the index in textData, but when it runs only the final value appears and the Stop button is not responsive. The cursor becomes a coloured wheel after about 3 seconds when it is moved off the Start button.
When the loop is run on the background thread, the Stop button is responsive but textData cannot be updated from the background thread.
What I would like is for textData to show the index value while the loop is running.
AppDelegate.h
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate>
#property (weak) IBOutlet NSTextField *textData;
#end
AppDelegate.m
#import "AppDelegate.h"
#interface AppDelegate ()
#property (strong) IBOutlet NSWindow *window;
#end
#implementation AppDelegate
#synthesize textData;
static bool stopBatch = false;
- (IBAction)runMain:(id)sender {
stopBatch = false;
[self runMain];
}
- (IBAction)stopClick:(id)sender {
stopBatch = true;
}
- (IBAction)runBackground:(id)sender {
stopBatch = false;
[self runBatchBackground];
}
-(void) runMain{
[textData setStringValue:#"Start"];
[textData displayIfNeeded];
NSString * iString = #"0";
for (int i=0;i<=10000 ;i++)
{
iString= [NSString stringWithFormat: #"%d",i];
[textData setStringValue:iString];
[textData displayIfNeeded];
if(stopBatch)
{
break;
}
}
NSString *iStringFinal = iString;
}
-(void)runBatchBackground{
[textData setStringValue:#""];
NSString * __block iString = #"0";
dispatch_queue_t backgroundQueue = dispatch_queue_create("Network",nil);
dispatch_async(backgroundQueue, ^(void){
for (int i=0;i<=10000000 ;i++)
{
iString= [NSString stringWithFormat: #"%d",i];
//[self->_textData setStringValue:iString];
//[self->_textData displayIfNeeded];
if(stopBatch)
{
break;
}
}
NSString *iStringFinal = iString;
});
}
#end
After some experimentation I found a simpler solution than that kindly provided by #willeke. Using runMain code as shown below, adding a timerCalled method and adding a class variable iVal allowed the Stop button action to be executed while the loop was running. It appears that the 10000 timer requests are queued and then executed without blocking the main loop (and access to user controls) until timerCalled is exited using a return statement as shown. Is there anything wrong with this approach?
-(void) runMain{
for (int i=0;i<10000 ;i++)
{
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(timerCalled) userInfo:nil repeats:NO];
}
}
-(void)timerCalled{
if(stopBatch) return;
for (int i=0;i<10;i++)
{
iVal++;
iString= [NSString stringWithFormat: #"%ld",iVal];
[textData setStringValue:iString];
}
}
Here you go
- (void)runBatchBackground {
[self.textData setStringValue:#""];
NSString * __block iString = #"0";
dispatch_queue_t backgroundQueue = dispatch_queue_create("Network",nil);
dispatch_async(backgroundQueue, ^(void){
for (int i = 0; i <= 10000000; i++)
{
// Simulate some processing
// If the code on the background thread runs faster than the code
// on the main thread, then the main thread is lagging behind and doesn't
// have time to process events.
[NSThread sleepForTimeInterval:0.25];
iString = [NSString stringWithFormat: #"%d",i];
// Execute UI code on the main thread.
dispatch_async(dispatch_get_main_queue(), ^{
[self.textData setStringValue:iString];
//[self.textData displayIfNeeded]; displayIfNeeded is not needed
});
if (self->stopBatch)
{
break;
}
}
});
}

Draw the contents of an array modified in the background

I have a background thread that runs and modifies the contents of a NSMutableArray within an object. This takes a long time to run (several hours) and I periodically want to draw the contents of an array within the drawRect of a NSView to check on progress and see the intermediate results.
My object has a protocol with a method called: didChange:
// How I start my background thread
[self performSelectorInBackground:#selector(startProcessing) withObject:nil];
- (void)startProcessing {
myObject.delegate = self;
[myObject start];
}
// My protocol implementation
- (void)myObjectDidChange:(myObjectClass *)sender {
[myView setNeedsDisplay:YES];
}
// My View's drawRect (pseudo code)
- (void)drawRect {
[myObject drawInContext:context];
}
All works, except that the NSMutableArray backing all this is being changed whilst the drawing takes place. How should I do this? Do I somehow pause the processing in the background thread whilst the update is taking place?
EDIT: This is the sort of display I am drawing (although much more complicated):
Any help appreciated.
If you are doing something in background thread and you want to update UI, its usually done on the main thread, so in your object did change you would do it, probably like this:
// My protocol implementation
- (void)myObjectDidChange:(myObjectClass *)sender {
dispatch_async(dispatch_get_main_queue(), ^{
[self drawRect]; //Or any drawing function you are trying to do
});
}
I have done it using NSLock to lock the outer loop of the start and the drawInContext methods. I am still not sure if this is the best approach and will not accept this answer for a few days in case there is a better answer out there.
- (void)start {
for(int i=0; i < MAX; i++) {
[self.updateLock lock];
....
[self.updateLock unlock];
}
}
- (void)drawInContext:(CGContextRef)context {
[self.updateLock lock];
...
[self.updateLock unlock];
}

Testing GUI components that use grand central dispatch

I'm trying to write unit tests for some gui components that use grand central dispatch. I'd like to call threaded code from the test, wait for it to finish, and then check the results on the gui object.
dispatch_queue_t myQueue = dispatch_queue_create();
- (void)refreshGui {
[self.button setEnabled:NO];
dispatch_async(myQueue, ^{
//operation of undetermined length
sleep(1);
dispatch_sync(dispatch_get_main_queue(), ^{
// GUI stuff that must be on the main thread,
// I want this to be done before I check results in my tests.
[self.button setEnabled:YES];
});
});
}
In my tests, I want to do something like this:
-(void)testRefreshGui {
[object refreshGui];
[object blockUntilThreadedOperationIsDone];
STAssertTrue([object isRefreshedProperly], #"did not refresh");
}
My first idea was to call something synchronously on the relevant queue, like this. Unfortunately, this results in deadlock when called from the main queue (because there is a dispatch_sync() to the main queue in the gui code, and the test is also running on the main thread):
-(void)blockOnQueue:(dispatch_queue_t)q {
dispatch_sync(q, ^{});
}
Using a dispatch group with dispatch_group_wait(group, DISPATCH_TIME_FOREVER) also results in deadlock for the same reason.
A hack solution I came up with was this:
- (void)waitOnQueue:(dispatch_queue_t)q {
__block BOOL blocking = YES;
while (blocking) {
[NSRunLoop.mainRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
dispatch_sync(q, ^{});
blocking = NO;
});
}
}
Unfortunately, this 'solution' has the problem of pumping the main run loop and causing other tests to run, which breaks a number of things for me.
I also do not want to change the GUI code's dispatch_sync() to dispatch_async() because that's not the right behavior for this queue, and in the tests, the GUI code wouldn't be guaranteed to complete before the test checks the result either.
Thanks for any ideas!
You should decouple your need for the test to wait for GUI updates to run from how the main code path runs. In the first code block you posted, dispatch_sync is almost certainly the wrong approach (vs. dispatch_async) because you're going to block a background thread waiting on the main thread for no reason (there's no code after the dispatch_sync) this can lead to thread starvation (in deployment that is). I'm guessing that you made it dispatch_sync in an attempt to use the queue itself to interlock the two parallel tasks. If you are really committed to using that somewhat sub-optimal approach, you could do something like this:
- (void)testOne
{
SOAltUpdateView* view = [[SOAltUpdateView alloc] initWithFrame: NSMakeRect(0, 0, 100, 100)];
STAssertNotNil(view, #"View was nil");
STAssertEqualObjects(view.color, [NSColor redColor] , #"Initial color was wrong");
dispatch_queue_t q = dispatch_queue_create("test", 0);
dispatch_group_t group = dispatch_group_create();
view.queue = q;
// Run the operation
[view update];
// An operation we can wait on
dispatch_group_async(group, q, ^{ });
while (dispatch_group_wait(group, DISPATCH_TIME_NOW))
{
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, YES);
}
STAssertEqualObjects(view.color, [NSColor greenColor] , #"Updated color was wrong");
view.queue = nil;
[view release];
dispatch_release(group);
dispatch_release(q);
}
That was the approach that seemed closest to what you already had, but I came up with something that might be a little better/cleaner: A semaphore can do this interlocking for you, and with a little effort, you can make the intrusion on your actual GUI code pretty minimal. (Note: it will be effectively impossible to have no intrusion at all, because in order for two parallel tasks to interlock, they have to share something to interlock on -- something shared -- in your existing code it was the queue, here I'm using a semaphore.) Consider this contrived example: I've added a generic means for the test harness to push in a semaphore that can be used to notify it when the background operation completes. The "intrusion" on the code to be tested is limited to two macros.
NSObject+AsyncGUITestSupport.h:
#interface NSObject (AsyncGUITestSupport)
#property (nonatomic, readwrite, assign) dispatch_semaphore_t testCompletionSemaphore;
#end
#define OPERATION_BEGIN(...) do { dispatch_semaphore_t s = self.testCompletionSemaphore; if (s) dispatch_semaphore_wait(s, DISPATCH_TIME_NOW); } while(0)
#define OPERATION_END(...) do { dispatch_semaphore_t s = self.testCompletionSemaphore; if (s) dispatch_semaphore_signal(s); } while(0)
NSObject+AsyncGUITestSupport.m:
#import "NSObject+AsyncGUITestSupport.h"
#import <objc/runtime.h>
#implementation NSObject (AsyncGUITestSupport)
static void * const kTestingSemaphoreAssociatedStorageKey = (void*)&kTestingSemaphoreAssociatedStorageKey;
- (void)setTestCompletionSemaphore:(dispatch_semaphore_t)myProperty
{
objc_setAssociatedObject(self, kTestingSemaphoreAssociatedStorageKey, myProperty, OBJC_ASSOCIATION_ASSIGN);
}
- (dispatch_semaphore_t)testCompletionSemaphore
{
return objc_getAssociatedObject(self, kTestingSemaphoreAssociatedStorageKey);
}
#end
SOUpdateView.h
#interface SOUpdateView : NSView
#property (nonatomic, readonly, retain) NSColor* color;
- (void)update;
#end
SOUpdateView.m
#import "SOUpdateView.h"
#import "NSObject+AsyncGUITestSupport.h"
#implementation SOUpdateView
{
NSUInteger _count;
}
- (NSColor *)color
{
NSArray* colors = #[ [NSColor redColor], [NSColor greenColor], [NSColor blueColor] ];
#synchronized(self)
{
return colors[_count % colors.count];
}
}
- (void)drawRect:(NSRect)dirtyRect
{
[self.color set];
NSRectFill(dirtyRect);
}
- (void)update
{
OPERATION_BEGIN();
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
#synchronized(self)
{
_count++;
}
dispatch_async(dispatch_get_main_queue(), ^{
[self setNeedsDisplay: YES];
OPERATION_END();
});
});
}
#end
And then the test harness:
#import "TestSOTestGUI.h"
#import "SOUpdateView.h"
#import "NSObject+AsyncGUITestSupport.h"
#implementation TestSOTestGUI
- (void)testOne
{
SOUpdateView* view = [[SOUpdateView alloc] initWithFrame: NSMakeRect(0, 0, 100, 100)];
STAssertNotNil(view, #"View was nil");
STAssertEqualObjects(view.color, [NSColor redColor] , #"Initial color was wrong");
// Push in a semaphore...
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
view.testCompletionSemaphore = sem;
// Run the operation
[view update];
// Wait for the operation to finish.
while (dispatch_semaphore_wait(sem, DISPATCH_TIME_NOW))
{
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, YES);
}
// Clear out the semaphore
view.testCompletionSemaphore = nil;
STAssertEqualObjects(view.color, [NSColor greenColor] , #"Updated color was wrong");
}
#end
Hope this helps.

Objective-C, how to display determinate progress bar?

I would like to show the progress bar in my app as determinate rather than indeterminate. It doesn't work though when setting it up as determinate (works just fine for indeterminate). I've read some of the other answers to this, although they haven't worked. Any help would be appreciated - thanks!
#interface AppDelegate : NSObject <NSApplicationDelegate> {
IBOutlet NSProgressIndicator *showProgress;
}
- (IBAction)someMethod:(id)sender {
[showProgress setUsesThreadedAnimation:YES]; // This works
[showProgress startAnimation:self]; // This works
[showProgress setDoubleValue:(0.1)]; // This does not work
[showProgress setIndeterminate:NO]; // This does not work
[self doSomething];
[self doSomethingElse];
[self doSomethingMore];
....
[barProgress setDoubleValue:(1.0)]; // This does not work
[barProgress stopAnimation:self]; // This works
}
Updated code [working]:
- (IBAction)someMethod:(id)sender {
[showProgress setUsesThreadedAnimation:YES];
[showProgress startAnimation:self];
[showProgress setIndeterminate:NO];
[showProgress setDoubleValue:(0.1)];
[showProgress startAnimation:nil];
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(backgroundQueue, ^{
for (NSUInteger i = 0; i < 1; i++) {
dispatch_async(dispatch_get_main_queue(), ^{
[barProgress incrementBy:10.0];
});
}
[self doSomething];
[showProgress incrementBy:...];
dispatch_async(dispatch_get_main_queue(), ^{
[showProgress stopAnimation:nil];
});
});
[showProgress setDoubleValue:(1.0)];
}
Your doSomething method is blocking the main thread, which causes the run loop not to cycle, which in turn causes UI redraw to be blocked. The fix is to do the long running work in doSomething on a background queue, with periodic callbacks to the main queue to update the progress bar.
I have no idea what your doSomething method does, but for the sake of explanation, let's assume it runs a for loop with 100 steps. You'd implement it something like this:
- (void)doSomething
{
[showProgress startAnimation:nil];
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(backgroundQueue, ^{
for (NSUInteger i = 0; i < 100; i++) {
// Do whatever it is you need to do
dispatch_async(dispatch_get_main_queue(), ^{
[showProgress incrementBy:1.0];
});
}
// Done with long running task
dispatch_async(dispatch_get_main_queue(), ^{
[showProgress stopAnimation:nil];
});
});
}
Keep in mind, you still need to set the progress indicator up to be determinate, initialize its value and set an appropriate minValue and maxValue.
If you must do the work in doSomething on the main thread, it's possible to schedule small chunks of that work to be done on each run loop cycle, or to manually spin the run loop periodically as you're doing the work, but Grand Central Dispatch (GCD) would be my first choice if you can use it.

NSThread Timing Problem with GUI

I have a simple thread that runs an infinite loop. Eventually, the loop will check for serial data at a USB port a few thousand times per second, but at the moment, it just writes something to a custom class of NSTextView once every second.
int i;
for (i=1; i>0; i++)
{
[lock lock];
[[self textStorage] replaceCharactersInRange:NSMakeRange([[self textStorage] length], 0) withString:#"test\n"];
[lock unlock];
sleep(1);
}
The issue is that it writes really sporadically. It will do one or two, then wait ten seconds and spit ten of them out at once. If I replace the writing line with an NSLog(#"test"), it logs at nice even intervals. I have another test method in the main thread that accepts input into a text field and puts it into the text view, and doing this seems to update the text view to include the child thread's most recent writes. There shouldn't be anything interfering with it at this point anyway, but I've locked everything everywhere just to be sure. Thanks in advance.
You should always perform operations that affect the UI from the main thread. You can have the child thread create a temporary object that holds the results, and then use performSelectorOnMainThread:withObject:waitUntilDone: to call another method that will do the necessary modifications on the main thread.
NSString * const MDResultKey = #"MDResult";
- (void)someMethod {
//
int i;
for (i=1; i>0; i++) {
// if necessary, create an object to hold results
NSDictionary *results = [NSDictionary
dictionaryWithObjectsAndKeys:#"test", MDResultKey, nil];
[self performSelectorOnMainThread:#selector(updateUIWithResults:)
withObject:results waitUntilDone:NO];
sleep(1);
}
}
- (void)updateUIWithResults:(NSDictionary *)results {
NSString *result = [results objectForKey:MDResultKey];
[lock lock]; // ?
[[self textStorage] replaceCharactersInRange:
NSMakeRange([[self textStorage] length], 0) withString:result];
[lock unlock]; // ?
}
I'd personally be pretty wary of calling anything on an NSTextStorage on a background thread. I think NSTextView reacts to any NSTextStorage changes, and any UI code on a non-main thread is going to have unpredictable problems.
I would just send the new string to the main thread and call -replaceCharactersInRange: there.