使用dispatchsemaphore时泄漏

时间:2013-11-22 15:53:41

标签: ios memory-leaks grand-central-dispatch

我想在不同的队列中执行一些操作,所以我使用信号量。但是当我用仪器检查它时会出现泄漏。该帧是dispatch_semaphore_create。我使用ARC,之前检查时没有泄漏。代码如下:

dispatch_async(queue,^{
     dispatch_semaphore_t signal = dispatch_semaphore_create(1);
     for (int i=0;i<100;i++)
     {
            dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
            dispatch_async(queue,^{
                /* some actions */
                dispatch_semaphore_signal(signal);
            }); 
     }
});

代码就是这样,我输入时使用WINDOWS电脑,所以有一些拼写错误,对不起。

P.S。

我想逐个删除带有动画的表视图的单元格,如果我使用deleteRowsAtIndexpaths方法删除它们的索引路径数组,或者为了在主队列中删除它们,它们将被一起删除,而不是一个接一个,所以我用两个队列来做。代码如下:

dispatch_async(queue,^{

     dispatch_semaphore_t signal = dispatch_semaphore_create(1);

     for (int i=0;;i++)
     {
            dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);

            if (i == [indexPathsAry count])
            {
                 dispatch_semaphore_signal(finishAllSignal);
                 break;
            }

            dispatch_async(main_queue,^{
                NSIndexPath *indexPath = indexPathsAry[0];
                id item = items[indexPath.row];
                [items removeObject:item];

                [tableView beginUpdates];
                [tableView deleteRowsAtIndexPaths:indexPath withAnimation:...];
                [tableView endUpdates];
                dispatch_semaphore_signal(signal);
            }); 
     }
});

P.S。 对不起,NSIndexPath * indexPath = indexPathsAry [i];需要更改为NSIndexPath * indexPath = indexPathsAry [0];代码将逐个删除单元格而不是一起删除它们。它说行dispatch_semaphore_t signal = dispatch_semaphore_create(1)中有泄漏;但是当我在一个小型演示中测试这段代码时,它运行良好,所以我不知道为什么。起初我使用dispatch_sync而不是信号量,但它有时不同步工作,它让我很困惑。是因为我的项目中队列太多了吗?

2 个答案:

答案 0 :(得分:2)

您发布的代码中没有明显泄漏的原因。也就是说,使用背景队列和信号量可能不是最好的方法。在最好的情况下,它会在序列的大部分时间内阻塞两个线程(一个周期性地唤醒以使下一个删除操作入队,另一个等待finishAllSignal。)有更好的方法。哪种方法最好取决于您的确切效果。

例如,在通常的情况下,你的当前方法看起来会在开始删除操作/动画之间允许(至少)主旋流的一次旋转(参见下面的解释)。我的第一个想法是使用计时器开始每个删除一个或两个以后的帧。它看起来像这样:

NSArray* indexPathsAry = @[ ... ];
const NSTimeInterval intervalInSeconds = 1.0 / 30.0;

__block NSUInteger i = 0;

dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW,  (uint64_t)(intervalInSeconds * NSEC_PER_SEC), 0);
dispatch_source_set_event_handler(timer, ^{
    NSIndexPath *indexPath = indexPathsAry[i++];
    id item = items[indexPath.row];
    [items removeObject:item];

    [tableView beginUpdates];
    [tableView deleteRowsAtIndexPaths:indexPath withAnimation:...];
    [tableView endUpdates];

    if (i >= indexPathsAry.count)
    {
        dispatch_source_cancel(timer);
    }
});

dispatch_source_set_cancel_handler(timer, ^{
    // whatever you want to happen when all the removes are done.
})
dispatch_resume(timer);

如果你真的需要保证运行循环完全/只有一次旋转(这是你的信号量方法FWIW无法保证)那么NSOperation可能是最简单的方法(因为主线程{{1}每个运行循环旋转只服务一个操作。)如果你需要“完成”行为(即NSOperationQueue),你可以使用finishAllSignal依赖项来实现。它可能看起来像这样:

NSOperation

这两种方法都不会遭受泄漏。此外,这两种方法中的任何一种方法都比将两个线程等待信号量更长时间更好。

更多详情:

以下是我认为发布的方法将要做的解释:

  • 后台线程将主线程的块排队,然后等待信号量进入休眠状态。
  • 主线程runloop被libdispatch唤醒并最终执行块。
  • 该块删除该项,并在表视图上启动删除动画以删除该行。
  • 然后它发信号通知信号量,并完成。主线程当前正在执行(即没有休眠)并将继续处理来自主调度队列的块,直到不再存在,并最终继续运行循环,最终进入休眠状态等待下一个事件/唤醒
  • 由于信号量信令,后台线程稍后会被唤醒。此时,重复这些步骤。

需要注意的一点是主运行循环和后台线程之间没有强耦合。如果在阻塞之后主队列上有另一个不相关的块排队,那么后台队列可能会在不相关的块完成之前将下一个删除块放到主队列中。如果发生这种情况,那两个删除块可以在同一个runloop传递上执行,并且两个相应的项似乎同时被删除。在常见的情况下,这可能不会发生,但我的观点是信号量方法无法保证one-delete-per-runloop-pass行为,但我相信NSOperation* finishOp = [NSBlockOperation blockOperationWithBlock:^{ // ... whatever you want to have happen when all deletes have been processed. }]; for (NSIndexPath* toDelete in indexPathsAry) { NSOperation* op = [NSBlockOperation blockOperationWithBlock:^{ id item = items[toDelete.row]; [items removeObject: item]; [tableView beginUpdates]; [tableView deleteRowsAtIndexPaths: @[toDelete] withAnimation:...]; [tableView endUpdates]; }]; [finishOp addDependency: op]; [[NSOperationQueue mainQueue] addOperation: op]; } [[NSOperationQueue mainQueue] addOperation: finishOp]; 方法可以保证

老实说,计时器方法可能是最好的,因为它在语义上捕获了你想要的内容,即一行接一行地删除它们之间的一些最小延迟。

答案 1 :(得分:1)

我不知道这可能会如何起作用,因为这个问题引发了我的一大堆问题:

  1. 我推断你的意图是为每个被单独删除的行设置动画,这不会这样做,因为deleteRowsAtIndexPaths只会启动动画。此代码将使它们看起来几乎同时被删除。

  2. 我没有看到这可能如何工作,因为当你迭代这个结构时,你正在改变结构(你的数组和tableview)。例如,当i == 0时,一切都很好,因为你删除了第一个项目,但是到了时间i == 1,你数组中的第二个项目现在是数组中的第一个项目,所以当你引用indexPathsAry[i]时,你现在抓住最初的第三个项目,因为最初的第二个项目现在是indexPathsAry[0]

  3. deleteRowsAtIndexPaths方法采用索引路径数组,而不是单个索引路径。

  4. 这里使用信号量是完全没必要的。更合乎逻辑的模式是:

    dispatch_queue_t queue = dispatch_queue_create("com.company.app.tableupdate", 0);
    for (int i = 0; i < count; i++)
    {
        dispatch_async(queue, ^{
            dispatch_sync(dispatch_get_main_queue(), ^{
                // do some stuff to the table
            });
            [NSThread sleepForTimeInterval:0.1]; // if you want a little delay before the next one
        });
    }
    

    这会创建一个串行队列,向该队列添加一堆任务,这些任务本身在主队列上同步执行某些操作,然后在启动下一个队列之前等待。这达到了我想象的相同效果。

  5. 您正在浏览整个表格视图数据源,一次删除一个项目。如果项目数量没有大大超过可见行数,那就没问题,但我建议你真的想要(a)删除任何当前不可见的内容; (b)然后动画删除可见的。这将实现所需的视觉效果,同时不会让用户等待原始可见行的动画。

  6. 说完所有这些之后,我在这段代码示例中没有看到任何暗示泄漏的内容(这是您原来的问题)。您应该共享有关哪些对象被报告为泄漏的信息。


    顺便说一句,如果你想在从表格中删除所有内容之前从桌面视图中删除可见单元格的动画,你还可以执行以下操作:

    NSInteger count = [self.tableView.indexPathsForVisibleRows count];
    
    // first animate the cells flying off to the right
    
    [self.tableView.indexPathsForVisibleRows enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) {
        [UIView animateWithDuration:0.25 delay:idx * 0.05 options:0 animations:^{
            UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
            cell.transform = CGAffineTransformMakeTranslation(self.view.frame.size.width, 0);
        } completion:^(BOOL finished) {
    
            // if it's the last one, empty the data source and refresh the table
    
            if (idx == (count - 1)) {
                [self emptyTableDataSource];
                [self.tableView reloadData];
            }
        }];
    }];
    
相关问题