I have a UITableView that, when items are s开发者_运维百科elected, loads a viewController, which inside it performs some operations in the background using performSelectorInBackground.
Everything works fine if you slowly tap items in the tableView (essentially allowing the operations preforming in background to finish). But when you select the items quickly, the app quickly returns some memory warnings until it crashes, usually after about 7 or 8 "taps" or selections.
Any idea why this would be? When I move my code from the background thread to the main thread, everything works fine as well. You just can't make the tableView selections as quickly because it's waiting for the operations to finish.
Code snippets:
//this is called from - (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
-(void) showLeaseView:(NSMutableDictionary *)selLease
{
LeaseDetailController *leaseController = [[LeaseDetailController alloc] initWithNibName:@"LeaseDetail" bundle:nil];
leaseController.lease = selLease;
//[leaseController loadData];
[detailNavController pushViewController:leaseController animated:NO];
[leaseController release];
}
//this is in LeaseDetailController
- (void)viewDidLoad {
[self performSelectorInBackground:@selector(getOptions) withObject:nil];
[super viewDidLoad];
}
-(void) getOptions
{
NSAutoreleasePool *apool = [[NSAutoreleasePool alloc] init];
NSArray *arrayOnDisk = [[NSArray alloc] initWithContentsOfFile:[appdel.settingsDir stringByAppendingPathComponent:@"optionData"]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(LEASE_ID contains[cd] %@)", [lease leaseId]];
self.options = [NSMutableArray arrayWithArray:[arrayOnDisk filteredArrayUsingPredicate:predicate]];
[arrayOnDisk release];
[apool release];
}
Every time you perform the getOptions selector in the background, what's really happening is a new thread is being created on your behalf, and the work is being done there. When the user taps your table cells a bunch of times in a row, a new thread is created each time to handle the work. If the work done by getOptions takes some time to complete, you will have multiple threads calling getOptions at the same time. That is to say, the system doesn't cancel previous requests to perform getOptions in the background.
If you assume that it takes N bytes of memory to perform the work done by getOptions, then if the user taps on five table cells in a row and getOptions doesn't finish right away, then you'll find that your app is using 5 * N bytes at that point. In contrast, when you change your app to call getOptions on the main thread, it has to wait for each call to getOptions to complete before it can call getOptions again. Thus when you do your work on the main thread you don't run into the situation where you're using 5 * N bytes of memory to do the work of five instances of getOptions simultaneously.
That's why you run out of memory when you do this work in the background and the user taps multiple table cells: you're doing multiple instances of the work, and each instance requires its own amount of memory, and when they all get added up, it's more than the system can spare.
It looks like you're just calling getOptions once when the user selects a table cell and navigates into a new view controller. Since the user will only be looking at one of these view controllers at a time, you don't really need to have multiple instances of getOptions going on simultaneously in the background. Instead, you want to cancel the previously-running instance before starting the new one. You can do this using an NSOperationQueue, like so:
- (NSOperationQueue *)operationQueue
{
static NSOperationQueue * queue = nil;
if (!queue) {
// Set up a singleton operation queue that only runs one operation at a time.
queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:1];
}
return queue;
}
//this is called from - (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
-(void) showLeaseView:(NSMutableDictionary *)selLease
{
LeaseDetailController *leaseController = [[LeaseDetailController alloc] initWithNibName:@"LeaseDetail" bundle:nil];
leaseController.lease = selLease;
// Cancel any pending operations. They'll be discarded from the queue if they haven't begun yet.
// The currently-running operation will have to finish before the next one can start.
NSOperationQueue * queue = [self operationQueue];
[queue cancelAllOperations];
// Note that you'll need to add a property called operationQueue of type NSOperationQueue * to your LeaseDetailController class.
leaseController.operationQueue = queue;
//[leaseController loadData];
[detailNavController pushViewController:leaseController animated:NO];
[leaseController release];
}
//this is in LeaseDetailController
- (void)viewDidLoad {
// Now we use the operation queue given to us in -showLeaseView:, above, to run our operation in the background.
// Using the block version of the API for simplicity.
[queue addOperationWithBlock:^{
[self getOptions];
}];
[super viewDidLoad];
}
-(void) getOptions
{
NSAutoreleasePool *apool = [[NSAutoreleasePool alloc] init];
NSArray *arrayOnDisk = [[NSArray alloc] initWithContentsOfFile:[appdel.settingsDir stringByAppendingPathComponent:@"optionData"]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(LEASE_ID contains[cd] %@)", [lease leaseId]];
NSMutableArray * resultsArray = [NSMutableArray arrayWithArray:[arrayOnDisk filteredArrayUsingPredicate:predicate]];
// Now that the work is done, pass the results back to ourselves, but do so on the main queue, which is equivalent to the main thread.
// This ensures that any UI work we may do in the setter for the options property is done on the right thread.
dispatch_async(dispatch_queue_get_main(), ^{
self.options = resultsArray;
});
[arrayOnDisk release];
[apool release];
}
精彩评论