Accessing large file in Objective-C, hangs the rest of the program whilst it does stuff
Hey. I'm somewhat new to Objective-C (for Mac, this is) and I'm writing an app that will open large files, parse them, etc.
Problem is, as the program completes various tasks, I wish to update a progress bar. But the program becomes unresponsive until each task 开发者_开发知识库is complete.
How can I get round this?
Thanks.
If you’re executing a processor-intensive task on the main thread — which is responsible for updating the user interface and processing keyboard/mouse events — your application can become irresponsive. Use one of the concurrency programming techniques available to Cocoa programmers to free the main thread from anything that might block it.
You'll want to perform your file I/O and parsing on a background thread. There a few ways to do this within Cocoa/Objective-C, the easiest probably being to use Grand Central Dispatch to perform the action with dispatch_async()
or a similar function. There are lots of questions related to performing tasks using GCD around SO, and here and here are good places to start.
As can be seen in the second article, there are ways of setting up completion blocks that will perform UI actions on the main thread when a given operation finishes in a dispatch thread.
Here a small example I've written recently, to do a task in a new Thread and updating the UI in the meanwhile. Basically, just replace the [NSThread sleepForTimeInterval:0.2] with some usefull code ;)
Header:
#import <Cocoa/Cocoa.h>
@interface ThreadingAppDelegate : NSObject <NSApplicationDelegate> {
@private
NSWindow *window;
NSSlider *slider;
NSProgressIndicator *progresIndicator;
NSTextField *textField;
NSButton *button;
NSButton *panicButton;
NSThread *aThread;
int theValue;
}
@property (assign) IBOutlet NSWindow *window;
@property (assign) IBOutlet NSSlider *slider;
@property (assign) IBOutlet NSProgressIndicator *progresIndicator;
@property (assign) IBOutlet NSTextField *textField;
@property (assign) IBOutlet NSButton *button;
@property (assign) IBOutlet NSButton *panicButton;
@property (assign) int theValue;
- (IBAction)startThread:(id)sender;
- (IBAction)stopThread:(id)sender;
- (void)moveStatusBar;
- (void)threadStart;
@end
Implementation:
#import "ThreadingAppDelegate.h"
@implementation ThreadingAppDelegate
@synthesize window;
@synthesize button;
@synthesize panicButton;
@synthesize slider;
@synthesize textField;
@synthesize progresIndicator;
@synthesize theValue;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
}
- (IBAction)startThread:(id)sender
{
[progresIndicator setDoubleValue:0.0];
[button setEnabled:NO];
//[NSThread detachNewThreadSelector:@selector(threadStart) toTarget:self withObject:nil];
aThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadStart) object:nil];
[aThread start];
[panicButton setEnabled:YES];
}
- (IBAction)stopThread:(id)sender
{
[progresIndicator setDoubleValue:0.0];
[aThread cancel];
[aThread release];
aThread = nil;
[button setEnabled:YES];
[panicButton setEnabled:NO];
}
- (void)moveStatusBar
{
[progresIndicator setDoubleValue:[progresIndicator doubleValue] + 0.5];
if ([progresIndicator doubleValue] == 100) {
[button setEnabled:YES];
[panicButton setEnabled:NO];
}
}
- (void)threadStart
{
while([progresIndicator doubleValue] <= 100.0) {
[self performSelectorOnMainThread:@selector(moveStatusBar) withObject:nil waitUntilDone:NO];
[NSThread sleepForTimeInterval:0.2];
if([[NSThread currentThread] isCancelled]) {
break;
}
}
}
@end
The UI:
A background thread isn't always the right answer.
If your task is CPU-bound, then a background thread makes sense, but then you must deal with thread-safety issues.
If your task is I/O-bound, then it makes much more sense to use an event-based API which will notify you when data is available. You can keep everything on the main thread (no need to resort to tricks for updating the UI) without blocking the main run loop.
See NSFileHandle's readInBackgroundAndNotify
for an example: it lets the OS fill a buffer and then posts a notification when new data is available. In the observing method you can do this:
- (void)dataReceived:(NSNotification *)notification
{
NSDictionary *info = [notification userInfo];
NSData *newData = [info objectForKey:NSFileHandleNotificationDataItem];
[allData appendData:newData];
[progressIndicator setValue:([allData length] / totalFileLength)];
}
Ideally, you should parse as you go, rather than just stuffing all the new data into memory, but that may not be feasible for your specific data structure.
精彩评论