我有一个UITableView,当选择项目时,它会加载一个viewController,在其中,它会使用performSelectorInBackground在后台执行一些操作。

如果您慢慢点按tableView中的项目,则一切正常(实质上是允许在后台执行的操作完成)。但是,当您快速选择项目时,应用程序通常会在大约7或8次“轻击”或选择之后迅速返回一些内存警告,直到崩溃。

知道为什么会这样吗?当我将代码从后台线程移到主线程时,一切也正常。您只是无法快速选择tableView,因为它正在等待操作完成。

代码段:

//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];
}

最佳答案

每次在后台执行getOptions选择器时,实际上是在为您创建一个新线程,并在那里完成工作。当用户连续多次点击表格单元格时,每次都会创建一个新线程来处理工作。如果getOptions完成的工作需要一些时间才能完成,那么您将有多个线程同时调用getOptions。也就是说,系统不会取消先前在后台执行getOptions的请求。

如果您假设要花费N字节的内存来执行getOptions完成的工作,那么如果用户连续点击五个表单元格并且getOptions没有立即完成,则您会发现您的应用程序正在使用5 *此时为N个字节。相反,当您更改应用程序以在主线程上调用getOptions时,它必须等待对getOptions的每次调用完成,然后才能再次调用getOptions。因此,当您在主线程上进行工作时,不会遇到使用5 * N字节的内存同时执行五个getOptions实例的工作的情况。

这就是为什么在后台执行此工作并且用户点击多个表单元格时会耗尽内存的原因:您正在执行工作的多个实例,每个实例都需要自己的内存量,并且它们都被累加起来,这不仅是系统所能提供的。

当用户选择一个表格单元格并导航到一个新的视图控制器时,您似乎只调用一次getOptions。由于用户一次只能查看这些视图控制器之一,因此您实际上并不需要在后台同时运行多个getOptions实例。相反,您要在启动新实例之前取消先前运行的实例。您可以使用NSOperationQueue执行此操作,如下所示:

- (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];
}

关于objective-c - 使用performSelectorInBackground时收到内存警告,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/5122635/

10-10 20:55