使用performSelectorInBackground时接收内存警告

时间:2011-02-25 21:05:10

标签: objective-c ios

我有一个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];
}

1 个答案:

答案 0 :(得分:2)

每次在后台执行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];
}